1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle
.MonoRail
.Views
.Brail
17 using System
.Collections
;
18 using System
.Collections
.Generic
;
20 using Boo
.Lang
.Compiler
;
21 using Boo
.Lang
.Compiler
.IO
;
22 using Boo
.Lang
.Compiler
.Steps
;
25 public class BrailPreProcessor
: AbstractCompilerStep
27 public const string ClosingQuoteReplacement
= "`^`";
28 public const string DoubleQuote
= "\"";
29 private readonly static IDictionary separators
= CreateSeparators();
30 private readonly BooViewEngine booViewEngine
;
31 private readonly IDictionary inputToCode
= new Hashtable();
33 public BrailPreProcessor(BooViewEngine booViewEngine
)
35 this.booViewEngine
= booViewEngine
;
38 private static IDictionary
CreateSeparators()
40 Hashtable seperators
= new Hashtable();
41 seperators
.Add("<?brail", "?>");
42 seperators
.Add("<%", "%>");
46 public string GetInputCode(ICompilerInput input
)
48 return (string) inputToCode
[input
];
51 public override void Run()
53 ArrayList processed
= new ArrayList();
54 foreach(ICompilerInput input
in Parameters
.Input
)
56 //if input.Name.Contains("empty"):
57 // System.Diagnostics.Debugger.Break()
58 using(TextReader reader
= input
.Open())
60 string code
= reader
.ReadToEnd();
61 if (booViewEngine
.ConditionalPreProcessingOnly(input
.Name
) == false ||
62 ShouldPreProcess(code
))
64 StringInput newInput
= new StringInput(input
.Name
, code
);
65 inputToCode
.Add(input
, code
);
66 processed
.Add(newInput
);
69 Parameters
.Input
.Clear();
70 foreach(StringInput input
in processed
)
72 Parameters
.Input
.Add(input
);
76 private static bool ShouldPreProcess(string code
)
78 foreach(DictionaryEntry entry
in separators
)
80 if (code
.Contains(entry
.Key
.ToString()))
86 public static string Booify(string code
)
90 return "output string.Empty\r\n";
92 StringWriter buffer
= new StringWriter();
96 DictionaryEntry seperators
= GetSeperators(code
);
97 start
= seperators
.Key
.ToString();
98 end
= seperators
.Value
.ToString();
102 index
= code
.IndexOf(start
, lastIndex
);
105 Output(buffer
, code
.Substring(lastIndex
, index
- lastIndex
));
106 int startReading
= index
+ start
.Length
;
107 lastIndex
= code
.IndexOf(end
, startReading
);
109 throw new MonoRailException("expected " + end
);
110 int lastIndexOffset
= end
.Length
;
111 if (code
[lastIndex
- 1] == '-')
115 if (EndTagEndsWithNewline(code
, lastIndex
+ lastIndexOffset
))
117 lastIndexOffset
+= 2;
121 buffer
.WriteLine(code
.Substring(startReading
, lastIndex
- startReading
));
122 lastIndex
+= lastIndexOffset
;
124 Output(buffer
, code
.Substring(lastIndex
));
125 return buffer
.ToString();
128 private static bool EndTagEndsWithNewline(string code
, int endIndex
)
130 return code
.Length
> endIndex
+ 2 && code
.Substring(endIndex
+ 1, 2) == "\r\n";
133 private static void Output(StringWriter buffer
, string code
)
135 if (code
.Length
== 0)
137 IList
<ExpressionPosition
> expressions
= GetExpressionsPositions(code
);
138 if (expressions
.Count
== 0)
140 OutputText(buffer
, code
);
145 foreach(ExpressionPosition position
in expressions
)
147 string text
= code
.Substring(start
, position
.Start
- start
);
148 OutputText(buffer
, text
);
149 string expression
= code
.Substring(position
.Start
+ 2, position
.End
- (position
.Start
+ 2));
150 OutputExpression(buffer
, expression
, position
.ShouldEscape
);
151 start
= position
.End
+ 1;
153 string remainingText
= code
.Substring(start
, code
.Length
- start
);
154 OutputText(buffer
, remainingText
);
157 private static void OutputText(StringWriter buffer
, string code
)
159 code
= EscapeInitialAndClosingDoubleQuotes(code
);
160 buffer
.Write("output \"\"\"");
162 buffer
.WriteLine("\"\"\"");
165 private static void OutputExpression(TextWriter buffer
, string code
, bool shouldEscape
)
168 buffer
.Write("OutputEscaped ");
170 buffer
.Write("output ");
171 buffer
.WriteLine(code
);
174 private static string EscapeInitialAndClosingDoubleQuotes(string code
)
176 if (code
.StartsWith(DoubleQuote
))
177 code
= ClosingQuoteReplacement
+ code
.Substring(DoubleQuote
.Length
);
178 if (code
.EndsWith(DoubleQuote
))
179 code
= code
.Substring(0, code
.Length
- DoubleQuote
.Length
) + ClosingQuoteReplacement
;
184 /// Will find all the (outer most ${} expressions in the code, and return their positions).
185 /// Smart enough to figure out $${} escaping, but not much more
187 private static IList
<ExpressionPosition
> GetExpressionsPositions(string code
)
189 List
<ExpressionPosition
> bracesPositions
= new List
<ExpressionPosition
>();
190 bool prevCharWasDollar
= false;
191 bool prevCharWasBang
= false;
192 for(int index
= 0; index
< code
.Length
; index
++)
194 if (code
[index
] == '{')
196 bracesPositions
.Add(new ExpressionPosition(index
- 1, -1, prevCharWasDollar
|| prevCharWasBang
, prevCharWasBang
));
198 if (code
[index
] == '}' && bracesPositions
.Count
> 0)
200 ExpressionPosition position
= bracesPositions
[bracesPositions
.Count
- 1];
201 if (ParentExpressionIsNotValid(bracesPositions
, bracesPositions
.Count
))
203 bracesPositions
.RemoveAt(bracesPositions
.Count
- 1);
205 else if (position
.End
== -1)
207 position
.End
= index
;
210 //handles escaping expressions with $$ as well
211 prevCharWasDollar
= code
[index
] == '$' && !prevCharWasDollar
;
212 prevCharWasBang
= code
[index
] == '!' && !prevCharWasBang
;
214 bracesPositions
.RemoveAll(delegate(ExpressionPosition obj
) { return !obj.PrevCharWasDollarOrBang; }
);
215 return bracesPositions
;
218 private static bool ParentExpressionIsNotValid(List
<ExpressionPosition
> bracesPositions
,
221 if (index
- 2 < 0) return false;
222 ExpressionPosition parentExpression
= bracesPositions
[index
- 2];
223 if (parentExpression
.PrevCharWasDollarOrBang
== false)
224 return ParentExpressionIsNotValid(bracesPositions
, index
- 1);
225 return parentExpression
.End
== -1;
228 private static DictionaryEntry
GetSeperators(string code
)
230 string start
= null, end
= null;
231 foreach(DictionaryEntry entry
in separators
)
233 if (code
.IndexOf(entry
.Key
as string, 0) != -1)
235 if (start
!= null && code
.IndexOf(entry
.Key
as string) != -1)
236 continue; //handle a shorthanded seperator.
237 // handle long seperator
238 if (start
!= null && entry
.Key
.ToString().IndexOf(start
as string) == -1)
240 throw new MonoRailException("Can't mix seperators in one file. Found both " + start
+ " and " + entry
.Key
);
242 start
= entry
.Key
.ToString();
243 end
= entry
.Value
.ToString();
247 if (start
== null) //default, doesn't really matter, since it won't be used.
249 foreach(DictionaryEntry entry
in separators
)
254 return new DictionaryEntry(start
, end
);
257 public static string UnescapeInitialAndClosingDoubleQuotes(string code
)
259 if (code
.StartsWith(ClosingQuoteReplacement
))
260 code
= DoubleQuote
+ code
.Substring(ClosingQuoteReplacement
.Length
);
261 if (code
.EndsWith(ClosingQuoteReplacement
))
262 code
= code
.Substring(0, code
.Length
- ClosingQuoteReplacement
.Length
) +
267 #region Nested type: ExpressionPosition
269 private class ExpressionPosition
271 private readonly bool prevCharWasDollarOrBang
;
272 private readonly bool shouldEscape
;
273 private readonly int start
;
276 public ExpressionPosition(int start
, int end
, bool prevCharWasDollarOrBang
, bool shouldEscape
)
280 this.prevCharWasDollarOrBang
= prevCharWasDollarOrBang
;
281 this.shouldEscape
= shouldEscape
;
286 get { return start; }
295 public bool PrevCharWasDollarOrBang
297 get { return prevCharWasDollarOrBang; }
300 public bool ShouldEscape
302 get { return shouldEscape; }