Minor style changes
[castle.git] / MonoRail / Castle.MonoRail.Views.Brail / BrailPreProcessor.cs
blob18b579b08aa745941c2a1f5f14227fa812d427cc
1 // Copyright 2004-2007 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;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.IO;
21 using System.Text;
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("<%", "%>");
45 return seperators;
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))
65 code = Booify(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()))
83 return true;
85 return false;
88 public static string Booify(string code)
90 if (code.Length == 0)
92 return "output string.Empty\r\n";
94 StringWriter buffer = new StringWriter();
95 int index = 0;
96 int lastIndex = 0;
97 string start, end;
98 DictionaryEntry seperators = GetSeperators(code);
99 start = seperators.Key.ToString();
100 end = seperators.Value.ToString();
102 while (index != -1)
104 index = code.IndexOf(start, lastIndex);
105 if (index == -1)
106 break;
107 Output(buffer, code.Substring(lastIndex, index - lastIndex));
108 int startReading = index + start.Length;
109 lastIndex = code.IndexOf(end, startReading);
110 if (lastIndex == -1)
111 throw new RailsException("expected " + end);
112 int lastIndexOffset = end.Length;
113 if (code[lastIndex - 1] == '-')
115 --lastIndex;
117 if (EndTagEndsWithNewline(code, lastIndex + lastIndexOffset))
119 lastIndexOffset += 2;
121 ++lastIndexOffset;
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)
138 return;
139 IList<ExpressionPosition> expressions = GetExpressionsPositions(code);
140 if (expressions.Count == 0)
142 OutputText(buffer, code);
143 return;
146 int start = 0;
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 \"\"\"");
163 buffer.Write(code);
164 buffer.WriteLine("\"\"\"");
167 private static void OutputExpression(TextWriter buffer, string code, bool shouldEscape)
169 if(shouldEscape)
170 buffer.Write("OutputEscaped ");
171 else
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;
182 return code;
185 /// <summary>
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
188 /// </summary>
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,
224 int index)
226 if (index - 2 < 0)
227 return false;
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;
237 private int end;
238 private readonly bool prevCharWasDollarOrBang;
239 private readonly bool shouldEscape;
241 public int Start
243 get { return start; }
246 public int End
248 get { return end; }
249 set { end = value; }
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)
264 this.start = start;
265 this.end = end;
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)
294 return entry;
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) +
306 DoubleQuote;
307 return code;