2003-07-16 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / System.Web / System.Web.Compilation / AspGenerator.cs
1 //
2 // System.Web.Compilation.AspGenerator
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //
7 // (C) 2002,2003 Ximian, Inc (http://www.ximian.com)
8 //
9 using System;
10 using System.Collections;
11 using System.IO;
12 using System.Text;
13 using System.Web.UI;
14 using System.Web.Util;
15
16 namespace System.Web.Compilation
17 {
18         class BuilderLocation
19         {
20                 public ControlBuilder Builder;
21                 public ILocation Location;
22
23                 public BuilderLocation (ControlBuilder builder, ILocation location)
24                 {
25                         this.Builder = builder;
26                         this.Location = location;
27                 }
28         }
29         
30         class BuilderLocationStack : Stack
31         {
32                 public override void Push (object o)
33                 {
34                         if (!(o is BuilderLocation))
35                                 throw new InvalidOperationException ();
36
37                         base.Push (o);
38                 }
39                 
40                 public virtual void Push (ControlBuilder builder, ILocation location)
41                 {
42                         BuilderLocation bl = new BuilderLocation (builder, location);
43                         Push (bl);
44                 }
45
46                 public new BuilderLocation Peek ()
47                 {
48                         return (BuilderLocation) base.Peek ();
49                 }
50
51                 public new BuilderLocation Pop ()
52                 {
53                         return (BuilderLocation) base.Pop ();
54                 }
55
56                 public ControlBuilder Builder {
57                         get { return Peek ().Builder; }
58                 }
59         }
60
61         class ParserStack
62         {
63                 Hashtable files;
64                 Stack parsers;
65                 AspParser current;
66
67                 public ParserStack ()
68                 {
69                         files = new Hashtable (); // may be this should be case sensitive for windows
70                         parsers = new Stack ();
71                 }
72                 
73                 public bool Push (AspParser parser)
74                 {
75                         if (files.Contains (parser.Filename))
76                                 return false;
77
78                         files [parser.Filename] = true;
79                         parsers.Push (parser);
80                         current = parser;
81                         return true;
82                 }
83
84                 public AspParser Pop ()
85                 {
86                         if (parsers.Count == 0)
87                                 return null;
88
89                         files.Remove (current.Filename);
90                         return (AspParser) parsers.Pop ();
91                 }
92                 
93                 public AspParser Parser {
94                         get { return current; }
95                 }
96
97                 public string Filename {
98                         get { return current.Filename; }
99                 }
100         }
101         
102         class AspGenerator
103         {
104                 ParserStack pstack;
105                 BuilderLocationStack stack;
106                 TemplateParser tparser;
107                 StringBuilder text;
108                 RootBuilder rootBuilder;
109                 bool inScript;
110                 ILocation location;
111                 static Hashtable emptyHash = new Hashtable ();
112
113                 public AspGenerator (TemplateParser tparser)
114                 {
115                         this.tparser = tparser;
116                         tparser.AddDependency (tparser.InputFile);
117                         text = new StringBuilder ();
118                         stack = new BuilderLocationStack ();
119                         rootBuilder = new RootBuilder (tparser);
120                         stack.Push (rootBuilder, null);
121                         tparser.RootBuilder = rootBuilder;
122                         pstack = new ParserStack ();
123                 }
124
125                 public AspParser Parser {
126                         get { return pstack.Parser; }
127                 }
128                 
129                 public string Filename {
130                         get { return pstack.Filename; }
131                 }
132                 
133                 BaseCompiler GetCompilerFromType ()
134                 {
135                         Type type = tparser.GetType ();
136                         if (type == typeof (PageParser))
137                                 return new PageCompiler ((PageParser) tparser);
138
139                         if (type == typeof (ApplicationFileParser))
140                                 return new GlobalAsaxCompiler ((ApplicationFileParser) tparser);
141
142                         if (type == typeof (UserControlParser))
143                                 return new UserControlCompiler ((UserControlParser) tparser);
144
145                         throw new Exception ("Got type: " + type);
146                 }
147                 
148                 void InitParser (string filename)
149                 {
150                         //FIXME: use the encoding of the file or the one specified in the machine.config/web.config file.
151                         StreamReader reader = new StreamReader (filename, Encoding.Default);
152                         AspParser parser = new AspParser (filename, reader);
153                         parser.Error += new ParseErrorHandler (ParseError);
154                         parser.TagParsed += new TagParsedHandler (TagParsed);
155                         parser.TextParsed += new TextParsedHandler (TextParsed);
156                         if (!pstack.Push (parser))
157                                 throw new ParseException (Location, "Infinite recursion detected including file: " + filename);
158                         tparser.AddDependency (filename);
159                 }
160
161                 void DoParse ()
162                 {
163                         pstack.Parser.Parse ();
164                         if (text.Length > 0)
165                                 FlushText ();
166
167                         pstack.Pop ();
168                 }
169
170                 public Type GetCompiledType ()
171                 {
172                         InitParser (Path.GetFullPath (tparser.InputFile));
173                         DoParse ();
174 #if DEBUG
175                         PrintTree (rootBuilder, 0);
176 #endif
177
178                         if (stack.Count > 1)
179                                 throw new ParseException (stack.Builder.location,
180                                                 "Expecting </" + stack.Builder.TagName + ">" + stack.Builder);
181
182                         BaseCompiler compiler = GetCompilerFromType ();
183
184                         return compiler.GetCompiledType ();
185                 }
186
187 #if DEBUG
188                 static void PrintTree (ControlBuilder builder, int indent)
189                 {
190                         if (builder == null)
191                                 return;
192
193                         string i = new string ('\t', indent);
194                         Console.Write (i);
195                         Console.WriteLine ("b: {0} id: {1} type: {2} parent: {3}",
196                                            builder, builder.ID, builder.ControlType, builder.parentBuilder);
197
198                         if (builder.Children != null)
199                         foreach (object o in builder.Children) {
200                                 if (o is ControlBuilder)
201                                         PrintTree ((ControlBuilder) o, indent++);
202                         }
203                 }
204 #endif
205                 
206                 static void PrintLocation (ILocation loc)
207                 {
208                         Console.WriteLine ("\tFile name: " + loc.Filename);
209                         Console.WriteLine ("\tBegin line: " + loc.BeginLine);
210                         Console.WriteLine ("\tEnd line: " + loc.EndLine);
211                         Console.WriteLine ("\tBegin column: " + loc.BeginColumn);
212                         Console.WriteLine ("\tEnd column: " + loc.EndColumn);
213                         Console.WriteLine ("\tPlainText: " + loc.PlainText);
214                         Console.WriteLine ();
215                 }
216
217                 void ParseError (ILocation location, string message)
218                 {
219                         throw new ParseException (location, message);
220                 }
221
222                 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
223                 {
224                         this.location = new Location (location);
225                         if (tparser != null)
226                                 tparser.Location = location;
227
228                         if (text.Length != 0)
229                                 FlushText ();
230
231                         if (0 == String.Compare (tagid, "script", true)) {
232                                 if (ProcessScript (tagtype, attributes))
233                                         return;
234                         }
235
236                         switch (tagtype) {
237                         case TagType.Directive:
238                                 if (tagid == "")
239                                         tagid = tparser.DefaultDirectiveName;
240
241                                 tparser.AddDirective (tagid, attributes.GetDictionary (null));
242                                 break;
243                         case TagType.Tag:
244                                 if (!ProcessTag (tagid, attributes))
245                                         TextParsed (location, location.PlainText);
246                                 break;
247                         case TagType.Close:
248                                 if (!CloseControl (tagid))
249                                         TextParsed (location, location.PlainText);
250                                 break;
251                         case TagType.SelfClosing:
252                                 int count = stack.Count;
253                                 if (!ProcessTag (tagid, attributes)) {
254                                         TextParsed (location, location.PlainText);
255                                 } else if (stack.Count != count) {
256                                         CloseControl (tagid);
257                                 }
258                                 break;
259                         case TagType.DataBinding:
260                                 goto case TagType.CodeRender;
261                         case TagType.CodeRenderExpression:
262                                 goto case TagType.CodeRender;
263                         case TagType.CodeRender:
264                                 ProcessCode (tagtype, tagid, location);
265                                 break;
266                         case TagType.Include:
267                                 string file = attributes ["virtual"] as string;
268                                 bool isvirtual = (file != null);
269                                 if (!isvirtual)
270                                         file = attributes ["file"] as string;
271
272                                 if (isvirtual) {
273                                         file = tparser.MapPath (file);
274                                 } else if (!Path.IsPathRooted (file)) {
275                                         file = UrlUtils.Combine (tparser.BaseVirtualDir, file);
276                                 }
277
278                                 InitParser (file);
279                                 DoParse ();
280                                 break;
281                         default:
282                                 break;
283                         }
284                         //PrintLocation (location);
285                 }
286
287                 void TextParsed (ILocation location, string text)
288                 {
289                         if (text.IndexOf ("<%") != -1) {
290                                 if (this.text.Length > 0)
291                                         FlushText ();
292                                 CodeRenderParser r = new CodeRenderParser (text, stack.Builder);
293                                 r.AddChildren ();
294                                 return;
295                         }
296
297                         this.text.Append (text);
298                         //PrintLocation (location);
299                 }
300
301                 void FlushText ()
302                 {
303                         string t = text.ToString ();
304                         text.Length = 0;
305                         if (inScript) {
306                                 // TODO: store location
307                                 tparser.Scripts.Add (t);
308                                 return;
309                         }
310
311                         if (tparser.DefaultDirectiveName == "application" && t.Trim () != "")
312                                 throw new ParseException (location, "Content not valid for application file.");
313
314                         stack.Builder.AppendLiteralString (t);
315                 }
316
317                 bool ProcessTag (string tagid, TagAttributes atts)
318                 {
319                         ControlBuilder parent = stack.Builder;
320                         ControlBuilder builder = null;
321                         BuilderLocation bl = null;
322                         Hashtable htable = (atts != null) ? atts.GetDictionary (null) : emptyHash;
323                         if (stack.Count > 1) {
324                                 try {
325                                         builder = parent.CreateSubBuilder (tagid, htable, null, tparser, location);
326                                 } catch (TypeLoadException e) {
327                                         throw new ParseException (Location, "Type not found.", e);
328                                 } catch (Exception e) {
329                                         throw new ParseException (Location, e.Message, e);
330                                 }
331                         }
332
333                         if (builder == null && atts != null && atts.IsRunAtServer ()) {
334                                 try {
335                                         builder = rootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location);
336                                 } catch (TypeLoadException e) {
337                                         throw new ParseException (Location, "Type not found.", e);
338                                 } catch (Exception e) {
339                                         throw new ParseException (Location, e.Message, e);
340                                 }
341                         }
342                         
343                         if (builder == null)
344                                 return false;
345
346                         builder.location = location;
347                         builder.ID = htable ["id"] as string;
348                         if (builder.HasBody ()) {
349                                 if (builder is TemplateBuilder) {
350                                 //      push the id list
351                                 }
352                                 stack.Push (builder, location);
353                         } else {
354                                 // FIXME:ObjectTags...
355                                 parent.AppendSubBuilder (builder);
356                                 builder.CloseControl ();
357                         }
358
359                         return true;
360                 }
361
362                 bool ProcessScript (TagType tagtype, TagAttributes attributes)
363                 {
364                         if (tagtype != TagType.Close && attributes != null && attributes.IsRunAtServer ()) {
365                                 if (tagtype == TagType.Tag) {
366                                         Parser.VerbatimID = "script";
367                                         inScript = true;
368                                 } //else if (tagtype == TagType.SelfClosing)
369                                         // load script file here
370
371                                 return true;
372                         }
373
374                         bool result = inScript;
375                         inScript = false;
376
377                         return result;
378                 }
379
380                 bool CloseControl (string tagid)
381                 {
382                         ControlBuilder current = stack.Builder;
383                         string btag = current.TagName;
384                         if (0 != String.Compare (tagid, btag, true))
385                                 return false;
386
387                         // if (current is TemplateBuilder)
388                         //      pop from the id list
389                         current.CloseControl ();
390                         stack.Pop ();
391                         stack.Builder.AppendSubBuilder (current);
392                         return true;
393                 }
394
395                 bool ProcessCode (TagType tagtype, string code, ILocation location)
396                 {
397                         ControlBuilder b = null;
398                         if (tagtype == TagType.CodeRender)
399                                 b = new CodeRenderBuilder (code, false, location);
400                         else if (tagtype == TagType.CodeRenderExpression)
401                                 b = new CodeRenderBuilder (code, true, location);
402                         else if (tagtype == TagType.DataBinding)
403                                 b = new DataBindingBuilder (code, location);
404                         else
405                                 throw new HttpException ("Should never happen");
406
407                         stack.Builder.AppendSubBuilder (b);
408                         return true;
409                 }
410
411                 public ILocation Location {
412                         get { return location; }
413                 }
414
415                 // Used to get CodeRender tags in attribute values
416                 class CodeRenderParser
417                 {
418                         string str;
419                         ControlBuilder builder;
420
421                         public CodeRenderParser (string str, ControlBuilder builder)
422                         {
423                                 this.str = str;
424                                 this.builder = builder;
425                         }
426
427                         public void AddChildren ()
428                         {
429                                 int index = str.IndexOf ("<%");
430                                 if (index > 0) {
431                                         TextParsed (null, str.Substring (0, index));
432                                         str = str.Substring (index);
433                                 }
434
435                                 AspParser parser = new AspParser ("@@inner_string@@", new StringReader (str));
436                                 parser.Error += new ParseErrorHandler (ParseError);
437                                 parser.TagParsed += new TagParsedHandler (TagParsed);
438                                 parser.TextParsed += new TextParsedHandler (TextParsed);
439                                 parser.Parse ();
440                         }
441
442                         void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
443                         {
444                                 if (tagtype == TagType.CodeRender)
445                                         builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location));
446                                 else if (tagtype == TagType.CodeRenderExpression)
447                                         builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location));
448                                 else if (tagtype == TagType.DataBinding)
449                                         builder.AppendSubBuilder (new DataBindingBuilder (tagid, location));
450                                 else
451                                         builder.AppendLiteralString (location.PlainText);
452                         }
453
454                         void TextParsed (ILocation location, string text)
455                         {
456                                 builder.AppendLiteralString (text);
457                         }
458
459                         void ParseError (ILocation location, string message)
460                         {
461                                 throw new ParseException (location, message);
462                         }
463                 }
464         }
465 }
466