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