New test.
[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                 public Type GetCompiledType ()
273                 {
274                         Type type = (Type) HttpRuntime.Cache.Get ("@@Type" + tparser.InputFile);
275                         if (type != null) {
276                                 return type;
277                         }
278
279                         isApplication = tparser.DefaultDirectiveName == "application";
280
281                         Parse ();
282
283                         BaseCompiler compiler = GetCompilerFromType ();
284
285                         type = compiler.GetCompiledType ();
286                         CacheDependency cd = new CacheDependency ((string[])
287                                                         tparser.Dependencies.ToArray (typeof (string)));
288
289                         HttpRuntime.Cache.InsertPrivate ("@@Type" + tparser.InputFile, type, cd);
290                         return type;
291                 }
292
293 #if DEBUG
294                 static void PrintTree (ControlBuilder builder, int indent)
295                 {
296                         if (builder == null)
297                                 return;
298
299                         string i = new string ('\t', indent);
300                         Console.Write (i);
301                         Console.WriteLine ("b: {0} id: {1} type: {2} parent: {3}",
302                                            builder, builder.ID, builder.ControlType, builder.parentBuilder);
303
304                         if (builder.Children != null)
305                         foreach (object o in builder.Children) {
306                                 if (o is ControlBuilder)
307                                         PrintTree ((ControlBuilder) o, indent++);
308                         }
309                 }
310                 
311                 static void PrintLocation (ILocation loc)
312                 {
313                         Console.WriteLine ("\tFile name: " + loc.Filename);
314                         Console.WriteLine ("\tBegin line: " + loc.BeginLine);
315                         Console.WriteLine ("\tEnd line: " + loc.EndLine);
316                         Console.WriteLine ("\tBegin column: " + loc.BeginColumn);
317                         Console.WriteLine ("\tEnd column: " + loc.EndColumn);
318                         Console.WriteLine ("\tPlainText: " + loc.PlainText);
319                         Console.WriteLine ();
320                 }
321 #endif
322
323                 void ParseError (ILocation location, string message)
324                 {
325                         throw new ParseException (location, message);
326                 }
327
328                 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
329                 {
330                         this.location = new Location (location);
331                         if (tparser != null)
332                                 tparser.Location = location;
333
334                         // MS ignores tbody/thead
335                         if ((attributes == null || !attributes.IsRunAtServer ())) {
336                                 if (String.Compare (tagid, "tbody", true, CultureInfo.InvariantCulture) == 0 ||
337                                     String.Compare (tagid, "thead", true, CultureInfo.InvariantCulture) == 0)
338                                 return;
339                         }
340
341                         if (text.Length != 0)
342                                 FlushText ();
343
344                         if (0 == String.Compare (tagid, "script", true, CultureInfo.InvariantCulture)) {
345                                 bool in_script = (inScript || ignore_text);
346                                 if (in_script || (tagtype != TagType.Close && attributes != null)) {
347                                         if ((in_script || attributes.IsRunAtServer ()) && ProcessScript (tagtype, attributes))
348                                                 return;
349                                 }
350                         }
351
352                         switch (tagtype) {
353                         case TagType.Directive:
354                                 if (tagid == "")
355                                         tagid = tparser.DefaultDirectiveName;
356
357                                 tparser.AddDirective (tagid, attributes.GetDictionary (null));
358                                 break;
359                         case TagType.Tag:
360                                 if (ProcessTag (tagid, attributes, tagtype)) {
361                                         useOtherTags = true;
362                                         break;
363                                 }
364
365                                 if (useOtherTags) {
366                                         stack.Builder.EnsureOtherTags ();
367                                         stack.Builder.OtherTags.Add (tagid);
368                                 }
369
370                                 TextParsed (location, location.PlainText);
371                                 break;
372                         case TagType.Close:
373                                 bool notServer = (useOtherTags && TryRemoveTag (tagid, stack.Builder.OtherTags));
374                                 if (!notServer && CloseControl (tagid))
375                                         break;
376                                 
377                                 TextParsed (location, location.PlainText);
378                                 break;
379                         case TagType.SelfClosing:
380                                 int count = stack.Count;
381                                 if (!ProcessTag (tagid, attributes, tagtype)) {
382                                         TextParsed (location, location.PlainText);
383                                 } else if (stack.Count != count) {
384                                         CloseControl (tagid);
385                                 }
386                                 break;
387                         case TagType.DataBinding:
388                                 goto case TagType.CodeRender;
389                         case TagType.CodeRenderExpression:
390                                 goto case TagType.CodeRender;
391                         case TagType.CodeRender:
392                                 if (isApplication)
393                                         throw new ParseException (location, "Invalid content for application file.");
394                         
395                                 ProcessCode (tagtype, tagid, location);
396                                 break;
397                         case TagType.Include:
398                                 if (isApplication)
399                                         throw new ParseException (location, "Invalid content for application file.");
400                         
401                                 string file = attributes ["virtual"] as string;
402                                 bool isvirtual = (file != null);
403                                 if (!isvirtual)
404                                         file = attributes ["file"] as string;
405
406                                 if (isvirtual) {
407                                         file = tparser.MapPath (file);
408                                 } else {
409                                         file = GetIncludeFilePath (tparser.BaseDir, file);
410                                 }
411
412                                 Parse (file);
413                                 break;
414                         default:
415                                 break;
416                         }
417                         //PrintLocation (location);
418                 }
419
420                 static bool TryRemoveTag (string tagid, ArrayList otags)
421                 {
422                         if (otags == null || otags.Count == 0)
423                                 return false;
424
425                         for (int idx = otags.Count - 1; idx >= 0; idx--) {
426                                 string otagid = (string) otags [idx];
427                                 if (0 == String.Compare (tagid, otagid, true, CultureInfo.InvariantCulture)) {
428                                         do {
429                                                 otags.RemoveAt (idx);
430                                         } while (otags.Count - 1 >= idx);
431                                         return true;
432                                 }
433                         }
434                         return false;
435                 }
436
437                 static string GetIncludeFilePath (string basedir, string filename)
438                 {
439                         if (Path.DirectorySeparatorChar == '/')
440                                 filename = filename.Replace ("\\", "/");
441
442                         return Path.GetFullPath (Path.Combine (basedir, filename));
443                 }
444                 
445                 void TextParsed (ILocation location, string text)
446                 {
447                         if (ignore_text)
448                                 return;
449
450                         if (text.IndexOf ("<%") != -1 && !inScript) {
451                                 if (this.text.Length > 0)
452                                         FlushText ();
453                                 CodeRenderParser r = new CodeRenderParser (text, stack.Builder);
454                                 r.AddChildren ();
455                                 return;
456                         }
457
458                         this.text.Append (text);
459                         //PrintLocation (location);
460                 }
461
462                 void FlushText ()
463                 {
464                         string t = text.ToString ();
465                         text.Length = 0;
466                         if (inScript) {
467                                 // TODO: store location
468                                 tparser.Scripts.Add (t);
469                                 return;
470                         }
471
472                         if (tparser.DefaultDirectiveName == "application" && t.Trim () != "")
473                                 throw new ParseException (location, "Content not valid for application file.");
474
475                         ControlBuilder current = stack.Builder;
476                         current.AppendLiteralString (t);
477                         if (current.NeedsTagInnerText ()) {
478                                 tagInnerText.Append (t);
479                         }
480                 }
481
482                 bool ProcessTag (string tagid, TagAttributes atts, TagType tagtype)
483                 {
484                         if (isApplication) {
485                                 if (String.Compare (tagid, "object", true, CultureInfo.InvariantCulture) != 0)
486                                         throw new ParseException (location, "Invalid tag for application file.");
487                         }
488
489                         ControlBuilder parent = stack.Builder;
490                         ControlBuilder builder = null;
491                         Hashtable htable = (atts != null) ? atts.GetDictionary (null) : emptyHash;
492                         if (stack.Count > 1) {
493                                 try {
494                                         builder = parent.CreateSubBuilder (tagid, htable, null, tparser, location);
495                                 } catch (TypeLoadException e) {
496                                         throw new ParseException (Location, "Type not found.", e);
497                                 } catch (Exception e) {
498                                         throw new ParseException (Location, e.Message, e);
499                                 }
500                         }
501
502                         if (builder == null && atts != null && atts.IsRunAtServer ()) {
503                                 string id = htable ["id"] as string;
504                                 if (id != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (id))
505                                         throw new ParseException (Location, "'" + id + "' is not a valid identifier");
506                                         
507                                 try {
508                                         builder = rootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location);
509                                 } catch (TypeLoadException e) {
510                                         throw new ParseException (Location, "Type not found.", e);
511                                 } catch (Exception e) {
512                                         throw new ParseException (Location, e.Message, e);
513                                 }
514                         }
515                         
516                         if (builder == null)
517                                 return false;
518
519                         builder.location = location;
520                         builder.ID = htable ["id"] as string;
521                         if (typeof (HtmlForm).IsAssignableFrom (builder.ControlType)) {
522                                 if (inForm)
523                                         throw new ParseException (location, "Only one <form> allowed.");
524
525                                 inForm = true;
526                         }
527
528                         if (builder.HasBody () && !(builder is ObjectTagBuilder)) {
529                                 if (builder is TemplateBuilder) {
530                                 //      push the id list
531                                 }
532                                 stack.Push (builder, location);
533                         } else {
534                                 if (!isApplication && builder is ObjectTagBuilder) {
535                                         ObjectTagBuilder ot = (ObjectTagBuilder) builder;
536                                         if (ot.Scope != null && ot.Scope != "")
537                                                 throw new ParseException (location, "Scope not allowed here");
538
539                                         if (tagtype == TagType.Tag) {
540                                                 stack.Push (builder, location);
541                                                 return true;
542                                         }
543                                 }
544                                 
545                                 parent.AppendSubBuilder (builder);
546                                 builder.CloseControl ();
547                         }
548
549                         return true;
550                 }
551
552                 string ReadFile (string filename)
553                 {
554                         string realpath = tparser.MapPath (filename);
555                         using (StreamReader sr = new StreamReader (realpath, WebEncoding.FileEncoding)) {
556                                 string content = sr.ReadToEnd ();
557                                 return content;
558                         }
559                 }
560
561                 bool ProcessScript (TagType tagtype, TagAttributes attributes)
562                 {
563                         if (tagtype != TagType.Close) {
564                                 if (attributes != null && attributes.IsRunAtServer ()) {
565                                         CheckLanguage ((string) attributes ["language"]);
566                                         string src = (string) attributes ["src"];
567                                         if (src != null) {
568                                                 if (src == "")
569                                                         throw new ParseException (Parser,
570                                                                 "src cannot be an empty string");
571
572                                                 string content = ReadFile (src);
573                                                 inScript = true;
574                                                 TextParsed (Parser, content);
575                                                 FlushText ();
576                                                 inScript = false;
577                                                 if (tagtype != TagType.SelfClosing) {
578                                                         ignore_text = true;
579                                                         Parser.VerbatimID = "script";
580                                                 }
581                                         } else if (tagtype == TagType.Tag) {
582                                                 Parser.VerbatimID = "script";
583                                                 inScript = true;
584                                         }
585
586                                         return true;
587                                 } else {
588                                         if (tagtype != TagType.SelfClosing) {
589                                                 Parser.VerbatimID = "script";
590                                                 javascript = true;
591                                         }
592                                         TextParsed (location, location.PlainText);
593                                         return true;
594                                 }
595                         }
596
597                         bool result;
598                         if (inScript) {
599                                 result = inScript;
600                                 inScript = false;
601                         } else if (!ignore_text) {
602                                 result = javascript;
603                                 javascript = false;
604                                 TextParsed (location, location.PlainText);
605                         } else {
606                                 ignore_text = false;
607                                 result = true;
608                         }
609
610                         return result;
611                 }
612
613                 bool CloseControl (string tagid)
614                 {
615                         ControlBuilder current = stack.Builder;
616                         string btag = current.TagName;
617                         if (String.Compare (btag, "tbody", true, CultureInfo.InvariantCulture) != 0 &&
618                             String.Compare (tagid, "tbody", true, CultureInfo.InvariantCulture) == 0) {
619                                 if (!current.ChildrenAsProperties) {
620                                         try {
621                                                 TextParsed (location, location.PlainText);
622                                                 FlushText ();
623                                         } catch {}
624                                 }
625                                 return true;
626                         }
627                         
628                         if (0 != String.Compare (tagid, btag, true, CultureInfo.InvariantCulture))
629                                 return false;
630
631                         // if (current is TemplateBuilder)
632                         //      pop from the id list
633                         if (current.NeedsTagInnerText ()) {
634                                 try { 
635                                         current.SetTagInnerText (tagInnerText.ToString ());
636                                 } catch (Exception e) {
637                                         throw new ParseException (current.location, e.Message, e);
638                                 }
639
640                                 tagInnerText.Length = 0;
641                         }
642
643                         if (typeof (HtmlForm).IsAssignableFrom (current.ControlType)) {
644                                 inForm = false;
645                         }
646
647                         current.CloseControl ();
648                         stack.Pop ();
649                         stack.Builder.AppendSubBuilder (current);
650                         return true;
651                 }
652
653                 bool ProcessCode (TagType tagtype, string code, ILocation location)
654                 {
655                         ControlBuilder b = null;
656                         if (tagtype == TagType.CodeRender)
657                                 b = new CodeRenderBuilder (code, false, location);
658                         else if (tagtype == TagType.CodeRenderExpression)
659                                 b = new CodeRenderBuilder (code, true, location);
660                         else if (tagtype == TagType.DataBinding)
661                                 b = new DataBindingBuilder (code, location);
662                         else
663                                 throw new HttpException ("Should never happen");
664
665                         stack.Builder.AppendSubBuilder (b);
666                         return true;
667                 }
668
669                 public ILocation Location {
670                         get { return location; }
671                 }
672
673                 void CheckLanguage (string lang)
674                 {
675                         if (lang == null || lang == "")
676                                 return;
677
678                         if (String.Compare (lang, tparser.Language, true, CultureInfo.InvariantCulture) == 0)
679                                 return;
680
681 #if NET_2_0
682                         CompilationSection section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
683                         if (section.Compilers[tparser.Language] != section.Compilers[lang])
684 #else
685                         CompilationConfiguration cfg = CompilationConfiguration.GetInstance (HttpContext.Current); 
686                         if (!cfg.Compilers.CompareLanguages (tparser.Language, lang))
687 #endif
688                                 throw new ParseException (Location,
689                                                 String.Format ("Trying to mix language '{0}' and '{1}'.", 
690                                                                 tparser.Language, lang));
691                 }
692
693                 // Used to get CodeRender tags in attribute values
694                 class CodeRenderParser
695                 {
696                         string str;
697                         ControlBuilder builder;
698
699                         public CodeRenderParser (string str, ControlBuilder builder)
700                         {
701                                 this.str = str;
702                                 this.builder = builder;
703                         }
704
705                         public void AddChildren ()
706                         {
707                                 int index = str.IndexOf ("<%");
708                                 if (index > 0) {
709                                         TextParsed (null, str.Substring (0, index));
710                                         str = str.Substring (index);
711                                 }
712
713                                 AspParser parser = new AspParser ("@@inner_string@@", new StringReader (str));
714                                 parser.Error += new ParseErrorHandler (ParseError);
715                                 parser.TagParsed += new TagParsedHandler (TagParsed);
716                                 parser.TextParsed += new TextParsedHandler (TextParsed);
717                                 parser.Parse ();
718                         }
719
720                         void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
721                         {
722                                 if (tagtype == TagType.CodeRender)
723                                         builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location));
724                                 else if (tagtype == TagType.CodeRenderExpression)
725                                         builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location));
726                                 else if (tagtype == TagType.DataBinding)
727                                         builder.AppendSubBuilder (new DataBindingBuilder (tagid, location));
728                                 else
729                                         builder.AppendLiteralString (location.PlainText);
730                         }
731
732                         void TextParsed (ILocation location, string text)
733                         {
734                                 builder.AppendLiteralString (text);
735                         }
736
737                         void ParseError (ILocation location, string message)
738                         {
739                                 throw new ParseException (location, message);
740                         }
741                 }
742         }
743 }
744