1 // Copyright 2004-2007 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
18 using System
.Collections
;
19 using System
.Collections
.Generic
;
22 using Boo
.Lang
.Compiler
;
23 using Boo
.Lang
.Compiler
.IO
;
24 using Boo
.Lang
.Compiler
.Steps
;
25 using Castle
.MonoRail
.Framework
;
27 public class BrailPreProcessor
: AbstractCompilerStep
29 public const string ClosingQuoteReplacement
= "`^`";
30 public const string DoubleQuote
= "\"";
31 private static IDictionary Seperators
= CreateSeperators();
32 private BooViewEngine booViewEngine
;
33 private IDictionary inputToCode
= new Hashtable();
35 public BrailPreProcessor(BooViewEngine booViewEngine
)
37 this.booViewEngine
= booViewEngine
;
40 private static IDictionary
CreateSeperators()
42 Hashtable seperators
= new Hashtable();
43 seperators
.Add("<?brail", "?>");
44 seperators
.Add("<%", "%>");
48 public string GetInputCode(ICompilerInput input
)
50 return (string)inputToCode
[input
];
53 public override void Run()
55 ArrayList processed
= new ArrayList();
56 foreach (ICompilerInput input
in Parameters
.Input
)
58 //if input.Name.Contains("empty"):
59 // System.Diagnostics.Debugger.Break()
60 using (TextReader reader
= input
.Open())
62 string code
= reader
.ReadToEnd();
63 if (this.booViewEngine
.ConditionalPreProcessingOnly(input
.Name
) == false ||
64 ShouldPreProcess(code
))
66 StringInput newInput
= new StringInput(input
.Name
, code
);
67 inputToCode
.Add(input
, code
);
68 processed
.Add(newInput
);
71 Parameters
.Input
.Clear();
72 foreach (StringInput input
in processed
)
74 Parameters
.Input
.Add(input
);
78 private bool ShouldPreProcess(string code
)
80 foreach (DictionaryEntry entry
in Seperators
)
82 if (code
.Contains(entry
.Key
.ToString()))
88 public static string Booify(string code
)
92 return "output string.Empty\r\n";
94 StringWriter buffer
= new StringWriter();
98 DictionaryEntry seperators
= GetSeperators(code
);
99 start
= seperators
.Key
.ToString();
100 end
= seperators
.Value
.ToString();
104 index
= code
.IndexOf(start
, lastIndex
);
107 Output(buffer
, code
.Substring(lastIndex
, index
- lastIndex
));
108 int startReading
= index
+ start
.Length
;
109 lastIndex
= code
.IndexOf(end
, startReading
);
111 throw new RailsException("expected " + end
);
112 int lastIndexOffset
= end
.Length
;
113 if (code
[lastIndex
- 1] == '-')
117 if (EndTagEndsWithNewline(code
, lastIndex
+ lastIndexOffset
))
119 lastIndexOffset
+= 2;
123 buffer
.WriteLine(code
.Substring(startReading
, lastIndex
- startReading
));
124 lastIndex
+= lastIndexOffset
;
126 Output(buffer
, code
.Substring(lastIndex
));
127 return buffer
.ToString();
130 private static bool EndTagEndsWithNewline(string code
, int endIndex
)
132 return code
.Length
> endIndex
+ 2 && code
.Substring(endIndex
+ 1, 2) == "\r\n";
135 private static void Output(StringWriter buffer
, string code
)
137 if (code
.Length
== 0)
139 IList
<ExpressionPosition
> expressions
= GetExpressionsPositions(code
);
140 if (expressions
.Count
== 0)
142 OutputText(buffer
, code
);
147 foreach (ExpressionPosition position
in expressions
)
149 string text
= code
.Substring(start
, position
.Start
- start
);
150 OutputText(buffer
, text
);
151 string expression
= code
.Substring(position
.Start
+ 2, position
.End
- (position
.Start
+ 2));
152 OutputExpression(buffer
, expression
, position
.ShouldEscape
);
153 start
= position
.End
+ 1;
155 string remainingText
= code
.Substring(start
, code
.Length
- start
);
156 OutputText(buffer
, remainingText
);
159 private static void OutputText(StringWriter buffer
, string code
)
161 code
= EscapeInitialAndClosingDoubleQuotes(code
);
162 buffer
.Write("output \"\"\"");
164 buffer
.WriteLine("\"\"\"");
167 private static void OutputExpression(TextWriter buffer
, string code
, bool shouldEscape
)
170 buffer
.Write("OutputEscaped ");
172 buffer
.Write("output ");
173 buffer
.WriteLine(code
);
176 private static string EscapeInitialAndClosingDoubleQuotes(string code
)
178 if (code
.StartsWith(DoubleQuote
))
179 code
= ClosingQuoteReplacement
+ code
.Substring(DoubleQuote
.Length
);
180 if (code
.EndsWith(DoubleQuote
))
181 code
= code
.Substring(0, code
.Length
- DoubleQuote
.Length
) + ClosingQuoteReplacement
;
186 /// Will find all the (outer most ${} expressions in the code, and return their positions).
187 /// Smart enough to figure out $${} escaping, but not much more
189 private static IList
<ExpressionPosition
> GetExpressionsPositions(string code
)
191 List
<ExpressionPosition
> bracesPositions
= new List
<ExpressionPosition
>();
192 bool prevCharWasDollar
= false;
193 bool prevCharWasBang
= false;
194 for (int index
= 0; index
< code
.Length
; index
++)
196 if (code
[index
] == '{')
198 bracesPositions
.Add(new ExpressionPosition(index
- 1, -1, prevCharWasDollar
|| prevCharWasBang
, prevCharWasBang
));
200 if (code
[index
] == '}' && bracesPositions
.Count
> 0)
202 ExpressionPosition position
= bracesPositions
[bracesPositions
.Count
- 1];
203 if (ParentExpressionIsNotValid(bracesPositions
, bracesPositions
.Count
))
205 bracesPositions
.RemoveAt(bracesPositions
.Count
- 1);
207 else if (position
.End
==-1)
209 position
.End
= index
;
212 //handles escaping expressions with $$ as well
213 prevCharWasDollar
= code
[index
] == '$' && !prevCharWasDollar
;
214 prevCharWasBang
= code
[index
] == '!' && !prevCharWasBang
;
216 bracesPositions
.RemoveAll(delegate(ExpressionPosition obj
)
218 return !obj
.PrevCharWasDollarOrBang
;
220 return bracesPositions
;
223 private static bool ParentExpressionIsNotValid(List
<ExpressionPosition
> bracesPositions
,
228 ExpressionPosition parentExpression
= bracesPositions
[index
- 2];
229 if (parentExpression
.PrevCharWasDollarOrBang
== false)
230 return ParentExpressionIsNotValid(bracesPositions
, index
- 1);
231 return parentExpression
.End
== -1;
234 private class ExpressionPosition
236 private readonly int start
;
238 private readonly bool prevCharWasDollarOrBang
;
239 private readonly bool shouldEscape
;
243 get { return start; }
252 public bool PrevCharWasDollarOrBang
254 get { return prevCharWasDollarOrBang; }
257 public bool ShouldEscape
259 get { return shouldEscape; }
262 public ExpressionPosition(int start
, int end
, bool prevCharWasDollarOrBang
, bool shouldEscape
)
266 this.prevCharWasDollarOrBang
= prevCharWasDollarOrBang
;
267 this.shouldEscape
= shouldEscape
;
271 private static DictionaryEntry
GetSeperators(string code
)
273 string start
= null, end
= null;
274 foreach (DictionaryEntry entry
in Seperators
)
276 if (code
.IndexOf(entry
.Key
as string, 0) != -1)
278 if (start
!= null && code
.IndexOf(entry
.Key
as string) != -1)
279 continue; //handle a shorthanded seperator.
280 // handle long seperator
281 if (start
!= null && entry
.Key
.ToString().IndexOf(start
as string) == -1)
283 throw new RailsException("Can't mix seperators in one file. Found both " + start
+ " and " + entry
.Key
);
285 start
= entry
.Key
.ToString();
286 end
= entry
.Value
.ToString();
290 if (start
== null) //default, doesn't really matter, since it won't be used.
292 foreach (DictionaryEntry entry
in Seperators
)
297 return new DictionaryEntry(start
, end
);
300 public static string UnescapeInitialAndClosingDoubleQuotes(string code
)
302 if (code
.StartsWith(ClosingQuoteReplacement
))
303 code
= DoubleQuote
+ code
.Substring(ClosingQuoteReplacement
.Length
);
304 if (code
.EndsWith(ClosingQuoteReplacement
))
305 code
= code
.Substring(0, code
.Length
- ClosingQuoteReplacement
.Length
) +