More working tests.
[castle.git] / MonoRail / Castle.MonoRail.Views.Brail / BrailPreProcessor.cs
blobbb19ac8eb9702a334def3151939ad1f65cfd4396
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
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
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
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;
19 using System.IO;
20 using Boo.Lang.Compiler;
21 using Boo.Lang.Compiler.IO;
22 using Boo.Lang.Compiler.Steps;
23 using Framework;
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("<%", "%>");
43 return seperators;
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))
63 code = Booify(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()))
81 return true;
83 return false;
86 public static string Booify(string code)
88 if (code.Length == 0)
90 return "output string.Empty\r\n";
92 StringWriter buffer = new StringWriter();
93 int index = 0;
94 int lastIndex = 0;
95 string start, end;
96 DictionaryEntry seperators = GetSeperators(code);
97 start = seperators.Key.ToString();
98 end = seperators.Value.ToString();
100 while(index != -1)
102 index = code.IndexOf(start, lastIndex);
103 if (index == -1)
104 break;
105 Output(buffer, code.Substring(lastIndex, index - lastIndex));
106 int startReading = index + start.Length;
107 lastIndex = code.IndexOf(end, startReading);
108 if (lastIndex == -1)
109 throw new MonoRailException("expected " + end);
110 int lastIndexOffset = end.Length;
111 if (code[lastIndex - 1] == '-')
113 --lastIndex;
115 if (EndTagEndsWithNewline(code, lastIndex + lastIndexOffset))
117 lastIndexOffset += 2;
119 ++lastIndexOffset;
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)
136 return;
137 IList<ExpressionPosition> expressions = GetExpressionsPositions(code);
138 if (expressions.Count == 0)
140 OutputText(buffer, code);
141 return;
144 int start = 0;
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 \"\"\"");
161 buffer.Write(code);
162 buffer.WriteLine("\"\"\"");
165 private static void OutputExpression(TextWriter buffer, string code, bool shouldEscape)
167 if (shouldEscape)
168 buffer.Write("OutputEscaped ");
169 else
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;
180 return code;
183 /// <summary>
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
186 /// </summary>
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,
219 int index)
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)
251 return entry;
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) +
263 DoubleQuote;
264 return code;
267 #region Nested type: ExpressionPosition
269 private class ExpressionPosition
271 private readonly bool prevCharWasDollarOrBang;
272 private readonly bool shouldEscape;
273 private readonly int start;
274 private int end;
276 public ExpressionPosition(int start, int end, bool prevCharWasDollarOrBang, bool shouldEscape)
278 this.start = start;
279 this.end = end;
280 this.prevCharWasDollarOrBang = prevCharWasDollarOrBang;
281 this.shouldEscape = shouldEscape;
284 public int Start
286 get { return start; }
289 public int End
291 get { return end; }
292 set { end = value; }
295 public bool PrevCharWasDollarOrBang
297 get { return prevCharWasDollarOrBang; }
300 public bool ShouldEscape
302 get { return shouldEscape; }
306 #endregion