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