2006-01-04 Chris Toshok <toshok@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
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 using System;
31 using System.Collections;
32 using System.CodeDom.Compiler;
33 using System.IO;
34 using System.Text;
35 using System.Web.Caching;
36 using System.Web.Configuration;
37 using System.Web.UI;
38 using System.Web.UI.HtmlControls;
39 using System.Web.Util;
40
41 namespace System.Web.Compilation
42 {
43         class BuilderLocation
44         {
45                 public ControlBuilder Builder;
46                 public ILocation Location;
47
48                 public BuilderLocation (ControlBuilder builder, ILocation location)
49                 {
50                         this.Builder = builder;
51                         this.Location = location;
52                 }
53         }
54
55         class BuilderLocationStack : Stack
56         {
57                 public override void Push (object o)
58                 {
59                         if (!(o is BuilderLocation))
60                                 throw new InvalidOperationException ();
61
62                         base.Push (o);
63                 }
64                 
65                 public virtual void Push (ControlBuilder builder, ILocation location)
66                 {
67                         BuilderLocation bl = new BuilderLocation (builder, location);
68                         Push (bl);
69                 }
70
71                 public new BuilderLocation Peek ()
72                 {
73                         return (BuilderLocation) base.Peek ();
74                 }
75
76                 public new BuilderLocation Pop ()
77                 {
78                         return (BuilderLocation) base.Pop ();
79                 }
80
81                 public ControlBuilder Builder {
82                         get { return Peek ().Builder; }
83                 }
84         }
85
86         class ParserStack
87         {
88                 Hashtable files;
89                 Stack parsers;
90                 AspParser current;
91
92                 public ParserStack ()
93                 {
94                         files = new Hashtable (); // may be this should be case sensitive for windows
95                         parsers = new Stack ();
96                 }
97                 
98                 public bool Push (AspParser parser)
99                 {
100                         if (files.Contains (parser.Filename))
101                                 return false;
102
103                         files [parser.Filename] = true;
104                         parsers.Push (parser);
105                         current = parser;
106                         return true;
107                 }
108
109                 public AspParser Pop ()
110                 {
111                         if (parsers.Count == 0)
112                                 return null;
113
114                         files.Remove (current.Filename);
115                         AspParser result = (AspParser) parsers.Pop ();
116                         if (parsers.Count > 0)
117                                 current = (AspParser) parsers.Peek ();
118                         else
119                                 current = null;
120
121                         return result;
122                 }
123                 
124                 public AspParser Parser {
125                         get { return current; }
126                 }
127
128                 public string Filename {
129                         get { return current.Filename; }
130                 }
131         }
132
133         class TagStack
134         {
135                 Stack tags;
136
137                 public TagStack ()
138                 {
139                         tags = new Stack ();
140                 }
141                 
142                 public void Push (string tagid)
143                 {
144                         tags.Push (tagid);
145                 }
146
147                 public string Pop ()
148                 {
149                         if (tags.Count == 0)
150                                 return null;
151
152                         return (string) tags.Pop ();
153                 }
154
155                 public bool CompareTo (string tagid)
156                 {
157                         if (tags.Count == 0)
158                                 return false;
159
160                         return 0 == String.Compare (tagid, (string) tags.Peek (), true);
161                 }
162                 
163                 public int Count {
164                         get { return tags.Count; }
165                 }
166
167                 public string Current {
168                         get { return (string) tags.Peek (); }
169                 }
170         }
171
172         class AspGenerator
173         {
174                 ParserStack pstack;
175                 BuilderLocationStack stack;
176                 TemplateParser tparser;
177                 StringBuilder text;
178                 RootBuilder rootBuilder;
179                 bool inScript, javascript;
180                 ILocation location;
181                 bool isApplication;
182                 StringBuilder tagInnerText = new StringBuilder ();
183                 static Hashtable emptyHash = new Hashtable ();
184                 bool inForm;
185                 bool useOtherTags;
186
187                 public AspGenerator (TemplateParser tparser)
188                 {
189                         this.tparser = tparser;
190                         text = new StringBuilder ();
191                         stack = new BuilderLocationStack ();
192                         rootBuilder = new RootBuilder (tparser);
193                         stack.Push (rootBuilder, null);
194                         tparser.RootBuilder = rootBuilder;
195                         pstack = new ParserStack ();
196                 }
197
198                 public AspParser Parser {
199                         get { return pstack.Parser; }
200                 }
201                 
202                 public string Filename {
203                         get { return pstack.Filename; }
204                 }
205                 
206                 BaseCompiler GetCompilerFromType ()
207                 {
208                         Type type = tparser.GetType ();
209                         if (type == typeof (PageParser))
210                                 return new PageCompiler ((PageParser) tparser);
211
212                         if (type == typeof (ApplicationFileParser))
213                                 return new GlobalAsaxCompiler ((ApplicationFileParser) tparser);
214
215                         if (type == typeof (UserControlParser))
216                                 return new UserControlCompiler ((UserControlParser) tparser);
217 #if NET_2_0
218                         if (type == typeof(MasterPageParser))
219                                 return new UserControlCompiler ((UserControlParser) tparser);
220 #endif
221
222                         throw new Exception ("Got type: " + type);
223                 }
224                 
225                 void InitParser (string filename)
226                 {
227                         StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
228                         AspParser parser = new AspParser (filename, reader);
229                         reader.Close ();
230                         parser.Error += new ParseErrorHandler (ParseError);
231                         parser.TagParsed += new TagParsedHandler (TagParsed);
232                         parser.TextParsed += new TextParsedHandler (TextParsed);
233                         if (!pstack.Push (parser))
234                                 throw new ParseException (Location, "Infinite recursion detected including file: " + filename);
235                         tparser.AddDependency (filename);
236                 }
237
238                 void DoParse ()
239                 {
240                         pstack.Parser.Parse ();
241                         if (text.Length > 0)
242                                 FlushText ();
243
244                         pstack.Pop ();
245                 }
246
247                 public Type GetCompiledType ()
248                 {
249                         Type type = (Type) HttpRuntime.Cache.Get ("@@Type" + tparser.InputFile);
250                         if (type != null) {
251                                 return type;
252                         }
253
254                         isApplication = tparser.DefaultDirectiveName == "application";
255                         InitParser (Path.GetFullPath (tparser.InputFile));
256
257                         DoParse ();
258 #if DEBUG
259                         PrintTree (rootBuilder, 0);
260 #endif
261
262                         if (stack.Count > 1)
263                                 throw new ParseException (stack.Builder.location,
264                                                 "Expecting </" + stack.Builder.TagName + "> " + stack.Builder);
265
266                         BaseCompiler compiler = GetCompilerFromType ();
267
268                         type = compiler.GetCompiledType ();
269                         CacheDependency cd = new CacheDependency ((string[])
270                                                         tparser.Dependencies.ToArray (typeof (string)));
271
272                         HttpRuntime.Cache.InsertPrivate ("@@Type" + tparser.InputFile, type, cd);
273                         return type;
274                 }
275
276 #if DEBUG
277                 static void PrintTree (ControlBuilder builder, int indent)
278                 {
279                         if (builder == null)
280                                 return;
281
282                         string i = new string ('\t', indent);
283                         Console.Write (i);
284                         Console.WriteLine ("b: {0} id: {1} type: {2} parent: {3}",
285                                            builder, builder.ID, builder.ControlType, builder.parentBuilder);
286
287                         if (builder.Children != null)
288                         foreach (object o in builder.Children) {
289                                 if (o is ControlBuilder)
290                                         PrintTree ((ControlBuilder) o, indent++);
291                         }
292                 }
293                 
294                 static void PrintLocation (ILocation loc)
295                 {
296                         Console.WriteLine ("\tFile name: " + loc.Filename);
297                         Console.WriteLine ("\tBegin line: " + loc.BeginLine);
298                         Console.WriteLine ("\tEnd line: " + loc.EndLine);
299                         Console.WriteLine ("\tBegin column: " + loc.BeginColumn);
300                         Console.WriteLine ("\tEnd column: " + loc.EndColumn);
301                         Console.WriteLine ("\tPlainText: " + loc.PlainText);
302                         Console.WriteLine ();
303                 }
304 #endif
305
306                 void ParseError (ILocation location, string message)
307                 {
308                         throw new ParseException (location, message);
309                 }
310
311                 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
312                 {
313                         this.location = new Location (location);
314                         if (tparser != null)
315                                 tparser.Location = location;
316
317                         if (text.Length != 0)
318                                 FlushText ();
319
320                         if (0 == String.Compare (tagid, "script", true)) {
321                                 if (inScript || (tagtype != TagType.Close && attributes != null)) {
322                                         if ((inScript || attributes.IsRunAtServer ()) && ProcessScript (tagtype, attributes))
323                                                 return;
324                                 }
325                         }
326
327                         switch (tagtype) {
328                         case TagType.Directive:
329                                 if (tagid == "")
330                                         tagid = tparser.DefaultDirectiveName;
331
332                                 tparser.AddDirective (tagid, attributes.GetDictionary (null));
333                                 break;
334                         case TagType.Tag:
335                                 if (ProcessTag (tagid, attributes, tagtype)) {
336                                         useOtherTags = true;
337                                         break;
338                                 }
339
340                                 if (useOtherTags) {
341                                         stack.Builder.EnsureOtherTags ();
342                                         stack.Builder.OtherTags.Add (tagid);
343                                 }
344
345                                 TextParsed (location, location.PlainText);
346                                 break;
347                         case TagType.Close:
348                                 bool notServer = (useOtherTags && TryRemoveTag (tagid, stack.Builder.OtherTags));
349                                 if (!notServer && CloseControl (tagid))
350                                         break;
351                                 
352                                 TextParsed (location, location.PlainText);
353                                 break;
354                         case TagType.SelfClosing:
355                                 int count = stack.Count;
356                                 if (!ProcessTag (tagid, attributes, tagtype)) {
357                                         TextParsed (location, location.PlainText);
358                                 } else if (stack.Count != count) {
359                                         CloseControl (tagid);
360                                 }
361                                 break;
362                         case TagType.DataBinding:
363                                 goto case TagType.CodeRender;
364                         case TagType.CodeRenderExpression:
365                                 goto case TagType.CodeRender;
366                         case TagType.CodeRender:
367                                 if (isApplication)
368                                         throw new ParseException (location, "Invalid content for application file.");
369                         
370                                 ProcessCode (tagtype, tagid, location);
371                                 break;
372                         case TagType.Include:
373                                 if (isApplication)
374                                         throw new ParseException (location, "Invalid content for application file.");
375                         
376                                 string file = attributes ["virtual"] as string;
377                                 bool isvirtual = (file != null);
378                                 if (!isvirtual)
379                                         file = attributes ["file"] as string;
380
381                                 if (isvirtual) {
382                                         file = tparser.MapPath (file);
383                                 } else {
384                                         file = GetIncludeFilePath (tparser.BaseDir, file);
385                                 }
386
387                                 InitParser (file);
388                                 DoParse ();
389                                 break;
390                         default:
391                                 break;
392                         }
393                         //PrintLocation (location);
394                 }
395
396                 static bool TryRemoveTag (string tagid, ArrayList otags)
397                 {
398                         if (otags == null || otags.Count == 0)
399                                 return false;
400
401                         int idx = otags.Count - 1;
402                         string otagid = (string) otags [idx];
403                         if (0 != String.Compare (tagid, otagid, true))
404                                 return false;
405
406                         otags.RemoveAt (idx);
407                         return true;
408                 }
409
410                 static string GetIncludeFilePath (string basedir, string filename)
411                 {
412                         if (Path.DirectorySeparatorChar == '/')
413                                 filename = filename.Replace ("\\", "/");
414
415                         return Path.GetFullPath (Path.Combine (basedir, filename));
416                 }
417                 
418                 void TextParsed (ILocation location, string text)
419                 {
420                         if (text.IndexOf ("<%") != -1 && !inScript) {
421                                 if (this.text.Length > 0)
422                                         FlushText ();
423                                 CodeRenderParser r = new CodeRenderParser (text, stack.Builder);
424                                 r.AddChildren ();
425                                 return;
426                         }
427
428                         this.text.Append (text);
429                         //PrintLocation (location);
430                 }
431
432                 void FlushText ()
433                 {
434                         string t = text.ToString ();
435                         text.Length = 0;
436                         if (inScript) {
437                                 // TODO: store location
438                                 tparser.Scripts.Add (t);
439                                 return;
440                         }
441
442                         if (tparser.DefaultDirectiveName == "application" && t.Trim () != "")
443                                 throw new ParseException (location, "Content not valid for application file.");
444
445                         ControlBuilder current = stack.Builder;
446                         current.AppendLiteralString (t);
447                         if (current.NeedsTagInnerText ()) {
448                                 tagInnerText.Append (t);
449                         }
450                 }
451
452                 bool ProcessTag (string tagid, TagAttributes atts, TagType tagtype)
453                 {
454                         if ((atts == null || !atts.IsRunAtServer ()) && String.Compare (tagid, "tbody", true) == 0) {
455                                 // MS completely ignores tbody or, if runat="server", fails when compiling
456                                 if (stack.Count > 0)
457                                         return stack.Builder.ChildrenAsProperties;
458
459                                 return false;
460                         }
461
462                         if (isApplication) {
463                                 if (String.Compare (tagid, "object", true) != 0)
464                                         throw new ParseException (location, "Invalid tag for application file.");
465                         }
466
467                         ControlBuilder parent = stack.Builder;
468                         ControlBuilder builder = null;
469                         Hashtable htable = (atts != null) ? atts.GetDictionary (null) : emptyHash;
470                         if (stack.Count > 1) {
471                                 try {
472                                         builder = parent.CreateSubBuilder (tagid, htable, null, tparser, location);
473                                 } catch (TypeLoadException e) {
474                                         throw new ParseException (Location, "Type not found.", e);
475                                 } catch (Exception e) {
476                                         throw new ParseException (Location, e.Message, e);
477                                 }
478                         }
479
480                         if (builder == null && atts != null && atts.IsRunAtServer ()) {
481                                 string id = htable ["id"] as string;
482                                 if (id != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (id))
483                                         throw new ParseException (Location, "'" + id + "' is not a valid identifier");
484                                         
485                                 try {
486                                         builder = rootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location);
487                                 } catch (TypeLoadException e) {
488                                         throw new ParseException (Location, "Type not found.", e);
489                                 } catch (Exception e) {
490                                         throw new ParseException (Location, e.Message, e);
491                                 }
492                         }
493                         
494                         if (builder == null)
495                                 return false;
496
497                         builder.location = location;
498                         builder.ID = htable ["id"] as string;
499                         if (typeof (HtmlForm).IsAssignableFrom (builder.ControlType)) {
500                                 if (inForm)
501                                         throw new ParseException (location, "Only one <form> allowed.");
502
503                                 inForm = true;
504                         }
505
506                         if (builder.HasBody () && !(builder is ObjectTagBuilder)) {
507                                 if (builder is TemplateBuilder) {
508                                 //      push the id list
509                                 }
510                                 stack.Push (builder, location);
511                         } else {
512                                 if (!isApplication && builder is ObjectTagBuilder) {
513                                         ObjectTagBuilder ot = (ObjectTagBuilder) builder;
514                                         if (ot.Scope != null && ot.Scope != "")
515                                                 throw new ParseException (location, "Scope not allowed here");
516
517                                         if (tagtype == TagType.Tag) {
518                                                 stack.Push (builder, location);
519                                                 return true;
520                                         }
521                                 }
522                                 
523                                 parent.AppendSubBuilder (builder);
524                                 builder.CloseControl ();
525                         }
526
527                         return true;
528                 }
529
530                 bool ProcessScript (TagType tagtype, TagAttributes attributes)
531                 {
532                         if (tagtype != TagType.Close) {
533                                 if (attributes != null && attributes.IsRunAtServer ()) {
534                                         CheckLanguage ((string) attributes ["language"]);
535                                         if (tagtype == TagType.Tag) {
536                                                 Parser.VerbatimID = "script";
537                                                 inScript = true;
538                                         } //else if (tagtype == TagType.SelfClosing)
539                                                 // load script file here
540
541                                         return true;
542                                 } else {
543                                         if (tagtype != TagType.SelfClosing) {
544                                                 Parser.VerbatimID = "script";
545                                                 javascript = true;
546                                         }
547                                         TextParsed (location, location.PlainText);
548                                         return true;
549                                 }
550                         }
551
552                         bool result;
553                         if (inScript) {
554                                 result = inScript;
555                                 inScript = false;
556                         } else {
557                                 result = javascript;
558                                 javascript = false;
559                                 TextParsed (location, location.PlainText);
560                         }
561
562                         return result;
563                 }
564
565                 bool CloseControl (string tagid)
566                 {
567                         ControlBuilder current = stack.Builder;
568                         string btag = current.TagName;
569                         if (String.Compare (btag, "tbody", true) != 0 && String.Compare (tagid, "tbody", true) == 0) {
570                                 if (!current.ChildrenAsProperties) {
571                                         try {
572                                                 TextParsed (location, location.PlainText);
573                                                 FlushText ();
574                                         } catch {}
575                                 }
576                                 return true;
577                         }
578                         
579                         if (0 != String.Compare (tagid, btag, true))
580                                 return false;
581
582                         // if (current is TemplateBuilder)
583                         //      pop from the id list
584                         if (current.NeedsTagInnerText ()) {
585                                 try { 
586                                         current.SetTagInnerText (tagInnerText.ToString ());
587                                 } catch (Exception e) {
588                                         throw new ParseException (current.location, e.Message, e);
589                                 }
590
591                                 tagInnerText.Length = 0;
592                         }
593
594                         if (typeof (HtmlForm).IsAssignableFrom (current.ControlType)) {
595                                 inForm = false;
596                         }
597
598                         current.CloseControl ();
599                         stack.Pop ();
600                         stack.Builder.AppendSubBuilder (current);
601                         return true;
602                 }
603
604                 bool ProcessCode (TagType tagtype, string code, ILocation location)
605                 {
606                         ControlBuilder b = null;
607                         if (tagtype == TagType.CodeRender)
608                                 b = new CodeRenderBuilder (code, false, location);
609                         else if (tagtype == TagType.CodeRenderExpression)
610                                 b = new CodeRenderBuilder (code, true, location);
611                         else if (tagtype == TagType.DataBinding)
612                                 b = new DataBindingBuilder (code, location);
613                         else
614                                 throw new HttpException ("Should never happen");
615
616                         stack.Builder.AppendSubBuilder (b);
617                         return true;
618                 }
619
620                 public ILocation Location {
621                         get { return location; }
622                 }
623
624                 void CheckLanguage (string lang)
625                 {
626                         if (lang == null || lang == "")
627                                 return;
628
629                         if (String.Compare (lang, tparser.Language, true) == 0)
630                                 return;
631
632 #if CONFIGURATION_2_0
633                         CompilationSection section = (CompilationSection) WebConfigurationManager.GetWebApplicationSection ("system.web/compilation");
634                         if (section.Compilers[tparser.Language] != section.Compilers[lang])
635 #else
636                         CompilationConfiguration cfg = CompilationConfiguration.GetInstance (HttpContext.Current); 
637                         if (!cfg.Compilers.CompareLanguages (tparser.Language, lang))
638 #endif
639                                 throw new ParseException (Location,
640                                                 String.Format ("Trying to mix language '{0}' and '{1}'.", 
641                                                                 tparser.Language, lang));
642                 }
643
644                 // Used to get CodeRender tags in attribute values
645                 class CodeRenderParser
646                 {
647                         string str;
648                         ControlBuilder builder;
649
650                         public CodeRenderParser (string str, ControlBuilder builder)
651                         {
652                                 this.str = str;
653                                 this.builder = builder;
654                         }
655
656                         public void AddChildren ()
657                         {
658                                 int index = str.IndexOf ("<%");
659                                 if (index > 0) {
660                                         TextParsed (null, str.Substring (0, index));
661                                         str = str.Substring (index);
662                                 }
663
664                                 AspParser parser = new AspParser ("@@inner_string@@", new StringReader (str));
665                                 parser.Error += new ParseErrorHandler (ParseError);
666                                 parser.TagParsed += new TagParsedHandler (TagParsed);
667                                 parser.TextParsed += new TextParsedHandler (TextParsed);
668                                 parser.Parse ();
669                         }
670
671                         void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
672                         {
673                                 if (tagtype == TagType.CodeRender)
674                                         builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location));
675                                 else if (tagtype == TagType.CodeRenderExpression)
676                                         builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location));
677                                 else if (tagtype == TagType.DataBinding)
678                                         builder.AppendSubBuilder (new DataBindingBuilder (tagid, location));
679                                 else
680                                         builder.AppendLiteralString (location.PlainText);
681                         }
682
683                         void TextParsed (ILocation location, string text)
684                         {
685                                 builder.AppendLiteralString (text);
686                         }
687
688                         void ParseError (ILocation location, string message)
689                         {
690                                 throw new ParseException (location, message);
691                         }
692                 }
693         }
694 }
695