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