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