2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[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.Text.RegularExpressions;
38 using System.Web.Caching;
39 using System.Web.Configuration;
40 using System.Web.Hosting;
41 using System.Web.UI;
42 using System.Web.UI.HtmlControls;
43 using System.Web.Util;
44
45 #if NET_2_0
46 using System.Collections.Generic;
47 #endif
48
49 namespace System.Web.Compilation
50 {
51         class BuilderLocation
52         {
53                 public ControlBuilder Builder;
54                 public ILocation Location;
55
56                 public BuilderLocation (ControlBuilder builder, ILocation location)
57                 {
58                         this.Builder = builder;
59                         this.Location = location;
60                 }
61         }
62
63         class BuilderLocationStack : Stack
64         {
65                 public override void Push (object o)
66                 {
67                         if (!(o is BuilderLocation))
68                                 throw new InvalidOperationException ();
69
70                         base.Push (o);
71                 }
72                 
73                 public virtual void Push (ControlBuilder builder, ILocation location)
74                 {
75                         BuilderLocation bl = new BuilderLocation (builder, location);
76                         Push (bl);
77                 }
78
79                 public new BuilderLocation Peek ()
80                 {
81                         return (BuilderLocation) base.Peek ();
82                 }
83
84                 public new BuilderLocation Pop ()
85                 {
86                         return (BuilderLocation) base.Pop ();
87                 }
88
89                 public ControlBuilder Builder {
90                         get { return Peek ().Builder; }
91                 }
92         }
93
94         class ParserStack
95         {
96                 Hashtable files;
97                 Stack parsers;
98                 AspParser current;
99
100                 public ParserStack ()
101                 {
102                         files = new Hashtable (); // may be this should be case sensitive for windows
103                         parsers = new Stack ();
104                 }
105                 
106                 public bool Push (AspParser parser)
107                 {
108                         if (files.Contains (parser.Filename))
109                                 return false;
110
111                         files [parser.Filename] = true;
112                         parsers.Push (parser);
113                         current = parser;
114                         return true;
115                 }
116
117                 public AspParser Pop ()
118                 {
119                         if (parsers.Count == 0)
120                                 return null;
121
122                         files.Remove (current.Filename);
123                         AspParser result = (AspParser) parsers.Pop ();
124                         if (parsers.Count > 0)
125                                 current = (AspParser) parsers.Peek ();
126                         else
127                                 current = null;
128
129                         return result;
130                 }
131
132                 public int Count {
133                         get { return parsers.Count; }
134                 }
135                 
136                 public AspParser Parser {
137                         get { return current; }
138                 }
139
140                 public string Filename {
141                         get { return current.Filename; }
142                 }
143         }
144
145         class TagStack
146         {
147                 Stack tags;
148
149                 public TagStack ()
150                 {
151                         tags = new Stack ();
152                 }
153                 
154                 public void Push (string tagid)
155                 {
156                         tags.Push (tagid);
157                 }
158
159                 public string Pop ()
160                 {
161                         if (tags.Count == 0)
162                                 return null;
163
164                         return (string) tags.Pop ();
165                 }
166
167                 public bool CompareTo (string tagid)
168                 {
169                         if (tags.Count == 0)
170                                 return false;
171
172                         return 0 == String.Compare (tagid, (string) tags.Peek (), true, CultureInfo.InvariantCulture);
173                 }
174                 
175                 public int Count {
176                         get { return tags.Count; }
177                 }
178
179                 public string Current {
180                         get { return (string) tags.Peek (); }
181                 }
182         }
183
184         class AspGenerator
185         {
186 #if NET_2_0
187                 const int READ_BUFFER_SIZE = 8192;
188                 
189                 internal static Regex DirectiveRegex = new Regex (@"<%\s*@(\s*(?<attrname>\w[\w:]*(?=\W))(\s*(?<equal>=)\s*""(?<attrval>[^""]*)""|\s*(?<equal>=)\s*'(?<attrval>[^']*)'|\s*(?<equal>=)\s*(?<attrval>[^\s%>]*)|(?<equal>)(?<attrval>\s*?)))*\s*?%>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
190 #endif
191                 ParserStack pstack;
192                 BuilderLocationStack stack;
193                 TemplateParser tparser;
194                 StringBuilder text;
195                 RootBuilder rootBuilder;
196                 bool inScript, javascript, ignore_text;
197                 ILocation location;
198                 bool isApplication;
199                 StringBuilder tagInnerText = new StringBuilder ();
200                 static Hashtable emptyHash = new Hashtable ();
201                 bool inForm;
202                 bool useOtherTags;
203                 TagType lastTag;
204 #if NET_2_0
205                 AspComponentFoundry componentFoundry;
206                 Stream inputStream;
207
208                 public AspGenerator (TemplateParser tparser, AspComponentFoundry componentFoundry) : this (tparser)
209                 {
210                         this.componentFoundry = componentFoundry;
211                 }
212 #endif
213                 
214                 public AspGenerator (TemplateParser tparser)
215                 {
216                         this.tparser = tparser;
217                         text = new StringBuilder ();
218                         stack = new BuilderLocationStack ();
219
220 #if !NET_2_0
221                         rootBuilder = new RootBuilder (tparser);
222                         tparser.RootBuilder = rootBuilder;
223                         stack.Push (rootBuilder, null);
224 #endif
225                         pstack = new ParserStack ();
226                 }
227
228                 public RootBuilder RootBuilder {
229                         get { return rootBuilder; }
230                 }
231
232                 public AspParser Parser {
233                         get { return pstack.Parser; }
234                 }
235                 
236                 public string Filename {
237                         get { return pstack.Filename; }
238                 }
239
240 #if NET_2_0
241                 PageParserFilter PageParserFilter {
242                         get {
243                                 if (tparser == null)
244                                         return null;
245
246                                 return tparser.PageParserFilter;
247                         }
248                 }
249
250                 // KLUDGE WARNING
251                 //
252                 // The kludge to determine the base type of the to-be-generated ASP.NET class is
253                 // very unfortunate but with our current parser it is, unfortunately, necessary. The
254                 // reason for reading the entire file into memory and parsing it with a regexp is
255                 // that we need to read the main directive (i.e. <%@Page %>, <%@Control %> etc),
256                 // pass it to the page parser filter if it exists, and finally read the inherits
257                 // attribute of the directive to get access to the base type of the class to be
258                 // generated. On that type we check whether it is decorated with the
259                 // FileLevelControlBuilder attribute and, if yes, use the indicated type as the
260                 // RootBuilder. This is necessary for the ASP.NET MVC views using the "generic"
261                 // inherits declaration to work properly. Our current parser is not able to parse
262                 // the input file out of sequence (i.e. directives first, then the rest) so we need
263                 // to do what we do below, alas.
264                 Hashtable GetDirectiveAttributesDictionary (string skipKeyName, CaptureCollection names, CaptureCollection values)
265                 {
266                         var ret = new Hashtable (StringComparer.InvariantCultureIgnoreCase);
267
268                         int index = 0;
269                         string keyName;
270                         foreach (Capture c in names) {
271                                 keyName = c.Value;
272                                 if (String.Compare (skipKeyName, keyName, StringComparison.OrdinalIgnoreCase) == 0) {
273                                         index++;
274                                         continue;
275                                 }
276                                 
277                                 ret.Add (c.Value, values [index++].Value);
278                         }
279
280                         return ret;
281                 }
282
283                 string GetDirectiveName (CaptureCollection names)
284                 {
285                         string val;
286                         foreach (Capture c in names) {
287                                 val = c.Value;
288                                 if (Directive.IsDirective (val))
289                                         return val;
290                         }
291
292                         return tparser.DefaultDirectiveName;
293                 }
294
295                 int GetLineNumberForIndex (string fileContents, int index)
296                 {
297                         int line = 1;
298                         char c;
299                         bool foundCR = false;
300                         
301                         for (int pos = 0; pos < index; pos++) {
302                                 c = fileContents [pos];
303                                 if (c == '\n' || foundCR) {
304                                         line++;
305                                         foundCR = false;
306                                 }
307                                 
308                                 foundCR = (c == '\r');
309                         }
310
311                         return line;
312                 }
313
314                 int GetNumberOfLinesForRange (string fileContents, int index, int length)
315                 {
316                         int lines = 0;
317                         int stop = index + length;
318                         char c;
319                         bool foundCR = false;
320                         
321                         for (int pos = index; pos < stop; pos++) {
322                                 c = fileContents [pos];
323                                 if (c == '\n' || foundCR) {
324                                         lines++;
325                                         foundCR = false;
326                                 }
327
328                                 foundCR = (c == '\r');
329                         }
330
331                         return lines;
332                 }
333                 
334                 Type GetInheritedType (string fileContents, string filename)
335                 {
336                         MatchCollection matches = DirectiveRegex.Matches (fileContents);
337                         if (matches == null || matches.Count == 0)
338                                 return null;
339
340                         string wantedDirectiveName = tparser.DefaultDirectiveName.ToLower ();
341                         string directiveName;
342                         GroupCollection groups;
343                         CaptureCollection ccNames;
344                         
345                         foreach (Match match in matches) {
346                                 groups = match.Groups;
347                                 if (groups.Count < 6)
348                                         continue;
349
350                                 ccNames = groups [3].Captures;
351                                 directiveName = GetDirectiveName (ccNames);
352                                 if (String.IsNullOrEmpty (directiveName))
353                                         continue;
354                                 
355                                 if (String.Compare (directiveName.ToLower (), wantedDirectiveName, StringComparison.Ordinal) != 0)
356                                         continue;
357
358                                 var loc = new Location (null);
359                                 int index = match.Index;
360                                 
361                                 loc.Filename = filename;
362                                 loc.BeginLine = GetLineNumberForIndex (fileContents, index);
363                                 loc.EndLine = loc.BeginLine + GetNumberOfLinesForRange (fileContents, index, match.Length);
364                                 
365                                 tparser.Location = loc;
366                                 tparser.allowedMainDirectives = 2;
367                                 tparser.AddDirective (wantedDirectiveName, GetDirectiveAttributesDictionary (wantedDirectiveName, ccNames, groups [5].Captures));
368
369                                 return tparser.BaseType;
370                         }
371                         
372                         return null;
373                 }
374
375                 string ReadFileContents (Stream inputStream, string filename)
376                 {
377                         string ret = null;
378                         
379                         if (inputStream != null) {
380                                 if (inputStream.CanSeek) {
381                                         long curPos = inputStream.Position;
382                                         inputStream.Seek (0, SeekOrigin.Begin);
383
384                                         Encoding enc = WebEncoding.FileEncoding;
385                                         StringBuilder sb = new StringBuilder ();
386                                         byte[] buffer = new byte [READ_BUFFER_SIZE];
387                                         int nbytes;
388                                         
389                                         while ((nbytes = inputStream.Read (buffer, 0, READ_BUFFER_SIZE)) > 0)
390                                                 sb.Append (enc.GetString (buffer, 0, nbytes));
391                                         inputStream.Seek (curPos, SeekOrigin.Begin);
392                                         
393                                         ret = sb.ToString ();
394                                         sb.Length = 0;
395                                         sb.Capacity = 0;
396                                 } else {
397                                         FileStream fs = inputStream as FileStream;
398                                         if (fs != null) {
399                                                 string fname = fs.Name;
400                                                 try {
401                                                         if (File.Exists (fname))
402                                                                 ret = File.ReadAllText (fname);
403                                                 } catch {
404                                                         // ignore
405                                                 }
406                                         }
407                                 }
408                         }
409
410                         if (ret == null && !String.IsNullOrEmpty (filename) && String.Compare (filename, "@@inner_string@@", StringComparison.Ordinal) != 0) {
411                                 try {
412                                         if (File.Exists (filename))
413                                                 ret = File.ReadAllText (filename);
414                                 } catch {
415                                         // ignore
416                                 }
417                         }
418
419                         return ret;
420                 }
421                 
422                 Type GetRootBuilderType (Stream inputStream, string filename)
423                 {
424                         Type ret = null;
425                         string fileContents;
426
427                         if (tparser != null)
428                                 fileContents = ReadFileContents (inputStream, filename);
429                         else
430                                 fileContents = null;
431                         
432                         if (!String.IsNullOrEmpty (fileContents)) {
433                                 Type inheritedType = GetInheritedType (fileContents, filename);
434                                 fileContents = null;
435                                 if (inheritedType != null) {
436                                         FileLevelControlBuilderAttribute attr;
437                                         
438                                         try {
439                                                 object[] attrs = inheritedType.GetCustomAttributes (typeof (FileLevelControlBuilderAttribute), true);
440                                                 if (attrs != null && attrs.Length > 0)
441                                                         attr = attrs [0] as FileLevelControlBuilderAttribute;
442                                                 else
443                                                         attr = null;
444                                         } catch {
445                                                 attr = null;
446                                         }
447
448                                         ret = attr != null ? attr.BuilderType : null;
449                                 }
450                         }
451                         
452                         if (ret == null) {
453                                 if (tparser is PageParser)
454                                         return typeof (FileLevelPageControlBuilder);
455                                 else if (tparser is UserControlParser)
456                                         return typeof (FileLevelUserControlBuilder);
457                                 else
458                                         return typeof (RootBuilder);
459                         } else
460                                 return ret;
461                 }
462                 
463                 void CreateRootBuilder (Stream inputStream, string filename)
464                 {
465                         if (rootBuilder != null)
466                                 return;
467                         
468                         Type rootBuilderType = GetRootBuilderType (inputStream, filename);
469                         rootBuilder = Activator.CreateInstance (rootBuilderType) as RootBuilder;
470                         if (rootBuilder == null)
471                                 throw new HttpException ("Cannot create an instance of file-level control builder.");
472                         rootBuilder.Init (tparser, null, null, null, null, null);
473                         if (componentFoundry != null)
474                                 rootBuilder.Foundry = componentFoundry;
475                         
476                         stack.Push (rootBuilder, null);
477                         tparser.RootBuilder = rootBuilder;
478                 }
479 #endif
480                 
481                 BaseCompiler GetCompilerFromType ()
482                 {
483                         Type type = tparser.GetType ();
484                         if (type == typeof (PageParser))
485                                 return new PageCompiler ((PageParser) tparser);
486
487                         if (type == typeof (ApplicationFileParser))
488                                 return new GlobalAsaxCompiler ((ApplicationFileParser) tparser);
489
490                         if (type == typeof (UserControlParser))
491                                 return new UserControlCompiler ((UserControlParser) tparser);
492 #if NET_2_0
493                         if (type == typeof(MasterPageParser))
494                                 return new MasterPageCompiler ((MasterPageParser) tparser);
495 #endif
496
497                         throw new Exception ("Got type: " + type);
498                 }
499
500                 void InitParser (TextReader reader, string filename)
501                 {
502                         AspParser parser = new AspParser (filename, reader);
503                         parser.Error += new ParseErrorHandler (ParseError);
504                         parser.TagParsed += new TagParsedHandler (TagParsed);
505                         parser.TextParsed += new TextParsedHandler (TextParsed);
506 #if NET_2_0
507                         parser.ParsingComplete += new ParsingCompleteHandler (ParsingCompleted);
508                         tparser.AspGenerator = this;
509                         CreateRootBuilder (inputStream, filename);
510 #endif
511                         if (!pstack.Push (parser))
512                                 throw new ParseException (Location, "Infinite recursion detected including file: " + filename);
513
514                         if (filename != "@@inner_string@@") {
515                                 string arvp = Path.Combine (tparser.BaseVirtualDir, Path.GetFileName (filename));
516                                 if (VirtualPathUtility.IsAbsolute (arvp))
517                                         arvp = VirtualPathUtility.ToAppRelative (arvp);
518                                 
519                                 tparser.AddDependency (arvp);
520                         }
521                 }
522                 
523 #if NET_2_0
524                 void InitParser (string filename)
525                 {
526                         StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
527                         InitParser (reader, filename);
528                 }
529 #endif
530                 
531                 public void Parse (string file)
532                 {
533 #if ONLY_1_1
534                         Parse (file, true);
535 #else
536                         Parse (file, false);
537 #endif
538                 }
539                 
540                 public void Parse (TextReader reader, string filename, bool doInitParser)
541                 {
542                         try {
543                                 isApplication = tparser.DefaultDirectiveName == "application";
544
545                                 if (doInitParser)
546                                         InitParser (reader, filename);
547
548                                 pstack.Parser.Parse ();
549                                 if (text.Length > 0)
550                                         FlushText ();
551
552 #if NET_2_0
553                                 tparser.MD5Checksum = pstack.Parser.MD5Checksum;
554 #endif
555                                 pstack.Pop ();
556
557 #if DEBUG
558                                 PrintTree (RootBuilder, 0);
559 #endif
560
561                                 if (stack.Count > 1 && pstack.Count == 0)
562                                         throw new ParseException (stack.Builder.Location,
563                                                                   "Expecting </" + stack.Builder.TagName + "> " + stack.Builder);
564
565                         } finally {
566                                 if (reader != null)
567                                         reader.Close ();
568                         }
569                 }
570
571                 public void Parse (Stream stream, string filename, bool doInitParser)
572                 {
573 #if NET_2_0
574                         inputStream = stream;
575 #endif
576                         Parse (new StreamReader (stream, WebEncoding.FileEncoding), filename, doInitParser);
577                 }
578                 
579                 public void Parse (string filename, bool doInitParser)
580                 {
581                         StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
582                         Parse (reader, filename, doInitParser);
583                 }
584
585                 public void Parse ()
586                 {
587 #if NET_2_0
588                         string inputFile = tparser.InputFile;
589                         TextReader inputReader = tparser.Reader;
590
591                         try {                   
592                                 if (String.IsNullOrEmpty (inputFile)) {
593                                         StreamReader sr = inputReader as StreamReader;
594                                         if (sr != null) {
595                                                 FileStream fr = sr.BaseStream as FileStream;
596                                                 if (fr != null)
597                                                         inputFile = fr.Name;
598                                         }
599
600                                         if (String.IsNullOrEmpty (inputFile))
601                                                 inputFile = "@@inner_string@@";
602                                 }
603
604                                 if (inputReader != null) {
605                                         Parse (inputReader, inputFile, true);
606                                 } else {
607                                         if (String.IsNullOrEmpty (inputFile))
608                                                 throw new HttpException ("Parser input file is empty, cannot continue.");
609                                         inputFile = Path.GetFullPath (inputFile);
610                                         InitParser (inputFile);
611                                         Parse (inputFile);
612                                 }
613                         } finally {
614                                 if (inputReader != null)
615                                         inputReader.Close ();
616                         }
617 #else
618                         Parse (Path.GetFullPath (tparser.InputFile));
619 #endif
620                 }
621
622                 internal static void AddTypeToCache (ArrayList dependencies, string inputFile, Type type)
623                 {
624                         if (type == null || inputFile == null || inputFile.Length == 0)
625                                 return;
626
627                         if (dependencies != null && dependencies.Count > 0) {
628                                 string [] deps = (string []) dependencies.ToArray (typeof (string));
629                                 HttpContext ctx = HttpContext.Current;
630                                 HttpRequest req = ctx != null ? ctx.Request : null;
631                                 
632                                 if (req == null)
633                                         throw new HttpException ("No current context, cannot compile.");
634
635                                 for (int i = 0; i < deps.Length; i++)
636                                         deps [i] = req.MapPath (deps [i]);
637
638                                 HttpRuntime.InternalCache.Insert ("@@Type" + inputFile, type, new CacheDependency (deps));
639                         } else
640                                 HttpRuntime.InternalCache.Insert ("@@Type" + inputFile, type);
641                 }
642                 
643                 public Type GetCompiledType ()
644                 {
645                         Type type = (Type) HttpRuntime.InternalCache.Get ("@@Type" + tparser.InputFile);
646                         if (type != null) {
647                                 return type;
648                         }
649
650                         Parse ();
651
652                         BaseCompiler compiler = GetCompilerFromType ();
653                         
654                         type = compiler.GetCompiledType ();
655                         AddTypeToCache (tparser.Dependencies, tparser.InputFile, type);
656                         return type;
657                 }
658
659 #if DEBUG
660                 static void PrintTree (ControlBuilder builder, int indent)
661                 {
662                         if (builder == null)
663                                 return;
664
665                         string i = new string ('\t', indent);
666                         Console.Write (i);
667                         Console.WriteLine ("b: {0} id: {1} type: {2} parent: {3}",
668                                            builder, builder.ID, builder.ControlType, builder.ParentBuilder);
669
670                         if (builder.Children != null)
671                         foreach (object o in builder.Children) {
672                                 if (o is ControlBuilder)
673                                         PrintTree ((ControlBuilder) o, indent++);
674                         }
675                 }
676                 
677                 static void PrintLocation (ILocation loc)
678                 {
679                         Console.WriteLine ("\tFile name: " + loc.Filename);
680                         Console.WriteLine ("\tBegin line: " + loc.BeginLine);
681                         Console.WriteLine ("\tEnd line: " + loc.EndLine);
682                         Console.WriteLine ("\tBegin column: " + loc.BeginColumn);
683                         Console.WriteLine ("\tEnd column: " + loc.EndColumn);
684                         Console.WriteLine ("\tPlainText: " + loc.PlainText);
685                         Console.WriteLine ();
686                 }
687 #endif
688
689                 void ParseError (ILocation location, string message)
690                 {
691                         throw new ParseException (location, message);
692                 }
693
694                 // KLUDGE WARNING!!
695                 //
696                 // The code below (ProcessTagsInAttributes, ParseAttributeTag) serves the purpose to work
697                 // around a limitation of the current asp.net parser which is unable to parse server
698                 // controls inside client tag attributes. Since the architecture of the current
699                 // parser does not allow for clean solution of this problem, hence the kludge
700                 // below. It will be gone as soon as the parser is rewritten.
701                 //
702                 // The kludge supports only self-closing tags inside attributes.
703                 //
704                 // KLUDGE WARNING!!
705                 static readonly Regex runatServer=new Regex (@"<[\w:\.]+.*?runat=[""']?server[""']?.*?/>",
706                                                              RegexOptions.Compiled | RegexOptions.Singleline |
707                                                              RegexOptions.Multiline | RegexOptions.IgnoreCase |
708                                                              RegexOptions.CultureInvariant);
709                 bool ProcessTagsInAttributes (ILocation location, string tagid, TagAttributes attributes, TagType type)
710                 {
711                         if (attributes == null || attributes.Count == 0)
712                                 return false;
713                         
714                         Match match;
715                         Group group;
716                         string value;
717                         bool retval = false;
718                         int index, length;
719                         StringBuilder sb = new StringBuilder ();
720
721                         sb.AppendFormat ("\t<{0}", tagid);
722                         foreach (string key in attributes.Keys) {
723                                 value = attributes [key] as string;
724                                 if (value == null || value.Length < 16) { // optimization
725                                         sb.AppendFormat (" {0}=\"{1}\"", key, value);
726                                         continue;
727                                 }
728                                 
729                                 match = runatServer.Match (attributes [key] as string);
730                                 if (!match.Success) {
731                                         sb.AppendFormat (" {0}=\"{1}\"", key, value);
732                                         continue;
733                                 }
734                                 if (sb.Length > 0) {
735                                         TextParsed (location, sb.ToString ());
736                                         sb.Length = 0;
737                                 }
738                                 
739                                 retval = true;
740                                 group = match.Groups [0];
741                                 index = group.Index;
742                                 length = group.Length;
743
744                                 TextParsed (location, String.Format (" {0}=\"{1}", key, index > 0 ? value.Substring (0, index) : String.Empty));;
745                                 FlushText ();
746                                 ParseAttributeTag (group.Value);
747                                 if (index + length < value.Length)
748                                         TextParsed (location, value.Substring (index + length) + "\"");
749                                 else
750                                         TextParsed (location, "\"");
751                         }
752                         if (type == TagType.SelfClosing)
753                                 sb.Append ("/>");
754                         else
755                                 sb.Append (">");
756
757                         if (retval && sb.Length > 0)
758                                 TextParsed (location, sb.ToString ());
759                         
760                         return retval;
761                 }
762
763                 void ParseAttributeTag (string code)
764                 {
765                         AspParser parser = new AspParser ("@@attribute_tag@@", new StringReader (code));
766                         parser.Error += new ParseErrorHandler (ParseError);
767                         parser.TagParsed += new TagParsedHandler (TagParsed);
768                         parser.TextParsed += new TextParsedHandler (TextParsed);
769                         parser.Parse ();
770                         if (text.Length > 0)
771                                 FlushText ();
772                 }
773
774 #if NET_2_0
775                 void ParsingCompleted ()
776                 {
777                         PageParserFilter pfilter = PageParserFilter;
778                         if (pfilter == null)
779                                 return;
780
781                         pfilter.ParseComplete (RootBuilder);
782                 }
783 #endif
784
785                 void CheckIfIncludeFileIsSecure (string filePath)
786                 {
787                         if (filePath == null || filePath.Length == 0)
788                                 return;
789                         
790                         // a bit slow, but fully portable
791                         string newdir = null;
792                         Exception exception = null;
793                         try {
794                                 string origdir = Directory.GetCurrentDirectory ();
795                                 Directory.SetCurrentDirectory (Path.GetDirectoryName (filePath));
796                                 newdir = Directory.GetCurrentDirectory ();
797                                 Directory.SetCurrentDirectory (origdir);
798                                 if (newdir [newdir.Length - 1] != '/')
799                                         newdir += "/";
800                         } catch (DirectoryNotFoundException) {
801                                 return; // will be converted into 404
802                         } catch (FileNotFoundException) {
803                                 return; // as above
804                         } catch (Exception ex) {
805                                 // better safe than sorry
806                                 exception = ex;
807                         }
808
809                         if (exception != null || !StrUtils.StartsWith (newdir, HttpRuntime.AppDomainAppPath))
810                                 throw new ParseException (Location, "Files above the application's root directory cannot be included.");
811                 }
812                 
813                 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
814                 {
815                         bool tagIgnored;
816                         
817                         this.location = new Location (location);
818                         if (tparser != null)
819                                 tparser.Location = location;
820
821                         if (text.Length != 0)
822                                 FlushText (lastTag == TagType.CodeRender);
823
824                         if (0 == String.Compare (tagid, "script", true, CultureInfo.InvariantCulture)) {
825                                 bool in_script = (inScript || ignore_text);
826                                 if (in_script) {
827                                         if (ProcessScript (tagtype, attributes))
828                                                 return;
829                                 } else
830                                         if (ProcessScript (tagtype, attributes))
831                                                 return;
832                         }
833
834                         lastTag = tagtype;
835                         switch (tagtype) {
836                         case TagType.Directive:
837                                 if (tagid.Length == 0)
838                                         tagid = tparser.DefaultDirectiveName;
839
840                                 tparser.AddDirective (tagid, attributes.GetDictionary (null));
841                                 break;
842                         case TagType.Tag:
843                                 if (ProcessTag (location, tagid, attributes, tagtype, out tagIgnored)) {
844                                         if (!tagIgnored)
845                                                 useOtherTags = true;
846                                         break;
847                                 }
848
849                                 if (useOtherTags) {
850                                         stack.Builder.EnsureOtherTags ();
851                                         stack.Builder.OtherTags.Add (tagid);
852                                 }
853
854                                 {
855                                         string plainText = location.PlainText;
856                                         if (!ProcessTagsInAttributes (location, tagid, attributes, TagType.Tag))
857                                                 TextParsed (location, plainText);
858                                 }
859                                 break;
860                         case TagType.Close:
861                                 bool notServer = (useOtherTags && TryRemoveTag (tagid, stack.Builder.OtherTags));
862                                 if (!notServer && CloseControl (tagid))
863                                         break;
864                                 
865                                 TextParsed (location, location.PlainText);
866                                 break;
867                         case TagType.SelfClosing:
868                                 int count = stack.Count;
869                                 if (!ProcessTag (location, tagid, attributes, tagtype, out tagIgnored) && !tagIgnored) {
870                                         string plainText = location.PlainText;
871                                         if (!ProcessTagsInAttributes (location, tagid, attributes, TagType.SelfClosing))
872                                                 TextParsed (location, plainText);
873                                 } else if (stack.Count != count) {
874                                         CloseControl (tagid);
875                                 }
876                                 break;
877                         case TagType.DataBinding:
878                                 goto case TagType.CodeRender;
879                         case TagType.CodeRenderExpression:
880                                 goto case TagType.CodeRender;
881                         case TagType.CodeRender:
882                                 if (isApplication)
883                                         throw new ParseException (location, "Invalid content for application file.");
884                         
885                                 ProcessCode (tagtype, tagid, location);
886                                 break;
887                         case TagType.Include:
888                                 if (isApplication)
889                                         throw new ParseException (location, "Invalid content for application file.");
890                         
891                                 string file = attributes ["virtual"] as string;
892                                 bool isvirtual = (file != null);
893                                 if (!isvirtual)
894                                         file = attributes ["file"] as string;
895
896                                 if (isvirtual) {
897                                         bool parsed = false;
898 #if NET_2_0
899                                         VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
900
901                                         if (vpp.FileExists (file)) {
902                                                 VirtualFile vf = vpp.GetFile (file);
903                                                 if (vf != null) {
904                                                         Parse (vf.Open (), file, true);
905                                                         parsed = true;
906                                                 }
907                                         }
908 #endif
909                                         
910                                         if (!parsed)
911                                                 Parse (tparser.MapPath (file), true);
912                                 } else {
913                                         string includeFilePath = GetIncludeFilePath (tparser.ParserDir, file);
914                                         CheckIfIncludeFileIsSecure (includeFilePath);
915                                         tparser.PushIncludeDir (Path.GetDirectoryName (includeFilePath));
916                                         try {
917                                                 Parse (includeFilePath, true);
918                                         } finally {
919                                                 tparser.PopIncludeDir ();
920                                         }
921                                 }
922                                 
923                                 break;
924                         default:
925                                 break;
926                         }
927                         //PrintLocation (location);
928                 }
929
930                 static bool TryRemoveTag (string tagid, ArrayList otags)
931                 {
932                         if (otags == null || otags.Count == 0)
933                                 return false;
934
935                         for (int idx = otags.Count - 1; idx >= 0; idx--) {
936                                 string otagid = (string) otags [idx];
937                                 if (0 == String.Compare (tagid, otagid, true, CultureInfo.InvariantCulture)) {
938                                         do {
939                                                 otags.RemoveAt (idx);
940                                         } while (otags.Count - 1 >= idx);
941                                         return true;
942                                 }
943                         }
944                         return false;
945                 }
946
947                 static string GetIncludeFilePath (string basedir, string filename)
948                 {
949                         if (Path.DirectorySeparatorChar == '/')
950                                 filename = filename.Replace ("\\", "/");
951
952                         return Path.GetFullPath (Path.Combine (basedir, filename));
953                 }
954                 
955                 void TextParsed (ILocation location, string text)
956                 {
957                         if (ignore_text)
958                                 return;
959                         
960                         // Another gross hack - get rid of comments in the parsed text
961                         int textLen = text.Length;
962                         int textMaxIndex = textLen - 1;
963                         int commentStart = text.IndexOf ("<!--");
964                         int commentLastStart = 0;
965 #if NET_2_0
966                         List <int> commentRanges = null;
967 #else
968                         ArrayList commentRanges = null;
969 #endif
970
971                         while (commentStart != -1) {
972                                 int commentEnd = text.IndexOf ("-->", commentStart);
973
974                                 if (commentEnd == -1) {
975                                         if (commentStart == 0)
976                                                 return;
977                                         commentEnd = textMaxIndex;
978                                 }
979
980                                 if (commentRanges == null) {
981 #if NET_2_0
982                                         commentRanges = new List <int> ();
983 #else
984                                         commentRanges = new ArrayList ();
985 #endif
986                                 }
987
988                                 if (commentStart > commentLastStart) {
989                                         commentRanges.Add (commentLastStart);
990                                         commentRanges.Add (commentStart);
991                                 }
992
993                                 if (commentEnd == textMaxIndex)
994                                         break;
995                                 
996                                 commentLastStart = commentEnd + 3;
997                                 if (commentLastStart > textMaxIndex)
998                                         break;
999                                 
1000                                 commentStart = text.IndexOf ("<!--", commentLastStart);
1001                                 if (commentStart == -1) {
1002                                         int tailLength = textMaxIndex - commentLastStart;
1003                                         if (tailLength > 0) {
1004                                                 commentRanges.Add (commentLastStart);
1005                                                 commentRanges.Add (tailLength);
1006                                         }
1007                                         break;
1008                                 }
1009                         }
1010
1011                         if (commentRanges != null) {
1012                                 if (commentRanges.Count == 0)
1013                                         return;
1014                                 
1015                                 StringBuilder sb = new StringBuilder ();
1016                                 for (int i = 0; i < commentRanges.Count; i += 2) {
1017 #if NET_2_0
1018                                         sb.Append (text.Substring (commentRanges [i], commentRanges [i + 1]));
1019 #else
1020                                         sb.Append (text.Substring ((int)commentRanges [i], (int)commentRanges [i + 1]));
1021 #endif
1022                                 }
1023
1024                                 string noComments = sb.ToString ().Trim ();
1025                                 if (noComments.Length == 0)
1026                                         return;
1027
1028                                 text = noComments;
1029                         }
1030
1031                         // And again... the first one wins - if we have expressions and server-side
1032                         // controls together in one block of plain text, tough luck...
1033                         if (text.IndexOf ("<%") != -1 && !inScript) {
1034                                 if (this.text.Length > 0)
1035                                         FlushText (true);
1036                                 CodeRenderParser r = new CodeRenderParser (text, stack.Builder);
1037                                 r.AddChildren (this);
1038                                 return;
1039                         }
1040
1041                         int startIndex = 0, index = 0;
1042                         Match match;
1043
1044                         while (index > -1 && startIndex < textLen) {
1045                                 match = runatServer.Match (text, index);
1046                                         
1047                                 if (match.Success) {
1048                                         string value = match.Value;
1049                                         index = match.Index;
1050                                         if (index > startIndex)
1051                                                 this.text.Append (text.Substring (startIndex, index - startIndex));
1052                                         ParseAttributeTag (value);
1053                                         index += value.Length;
1054                                         startIndex = index;
1055                                 } else
1056                                         break;
1057
1058                                 if (index < textLen)
1059                                         index = text.IndexOf ('<', index);
1060                                 else
1061                                         break;
1062                         }
1063                         
1064                         this.text.Append (text.Substring (startIndex));
1065                         //PrintLocation (location);
1066                 }
1067
1068                 void FlushText ()
1069                 {
1070                         FlushText (false);
1071                 }
1072                 
1073                 void FlushText (bool ignoreEmptyString)
1074                 {
1075                         string t = text.ToString ();
1076                         text.Length = 0;
1077
1078                         if (ignoreEmptyString && t.Trim ().Length == 0)
1079                                 return;
1080                         
1081                         if (inScript) {
1082 #if NET_2_0
1083                                 PageParserFilter pfilter = PageParserFilter;
1084                                 if (pfilter != null && !pfilter.ProcessCodeConstruct (CodeConstructType.ScriptTag, t))
1085                                         return;
1086 #endif
1087                                 tparser.Scripts.Add (new ServerSideScript (t, new System.Web.Compilation.Location (tparser.Location)));
1088                                 return;
1089                         }
1090
1091                         if (tparser.DefaultDirectiveName == "application" && t.Trim () != "")
1092                                 throw new ParseException (location, "Content not valid for application file.");
1093
1094                         ControlBuilder current = stack.Builder;
1095                         current.AppendLiteralString (t);
1096                         if (current.NeedsTagInnerText ()) {
1097                                 tagInnerText.Append (t);
1098                         }
1099                 }
1100
1101 #if NET_2_0
1102                 bool BuilderHasOtherThan (Type type, ControlBuilder cb)
1103                 {
1104                         ArrayList al = cb.OtherTags;
1105                         if (al != null && al.Count > 0)
1106                                 return true;
1107                         
1108                         al = cb.Children;
1109                         if (al != null) {
1110                                 ControlBuilder tmp;
1111                                 
1112                                 foreach (object o in al) {
1113                                         if (o == null)
1114                                                 continue;
1115                                         
1116                                         tmp = o as ControlBuilder;
1117                                         if (tmp == null) {
1118                                                 string s = o as string;
1119                                                 if (s != null && String.IsNullOrEmpty (s.Trim ()))
1120                                                         continue;
1121                                                 
1122                                                 return true;
1123                                         }
1124                                         
1125                                         if (tmp is System.Web.UI.WebControls.ContentBuilderInternal)
1126                                                 continue;
1127                                         
1128                                         if (tmp.ControlType != typeof (System.Web.UI.WebControls.Content))
1129                                                 return true;
1130                                 }
1131                         }
1132
1133                         return false;
1134                 }
1135                 
1136                 bool OtherControlsAllowed (ControlBuilder cb)
1137                 {
1138                         if (cb == null)
1139                                 return true;
1140                         
1141                         if (!typeof (System.Web.UI.WebControls.Content).IsAssignableFrom (cb.ControlType))
1142                                 return true;
1143
1144                         if (BuilderHasOtherThan (typeof (System.Web.UI.WebControls.Content), RootBuilder))
1145                                 return false;
1146                         
1147                         return true;
1148                 }
1149 #endif
1150
1151                 public void AddControl (Type type, IDictionary attributes)
1152                 {
1153                         ControlBuilder parent = stack.Builder;
1154                         ControlBuilder builder = ControlBuilder.CreateBuilderFromType (tparser, parent, type, null, null,
1155                                                                                        attributes, location.BeginLine,
1156                                                                                        location.Filename);
1157                         if (builder != null)
1158                                 parent.AppendSubBuilder (builder);
1159                 }
1160                 
1161                 bool ProcessTag (ILocation location, string tagid, TagAttributes atts, TagType tagtype, out bool ignored)
1162                 {
1163                         ignored = false;
1164                         if (isApplication) {
1165                                 if (String.Compare (tagid, "object", true, CultureInfo.InvariantCulture) != 0)
1166                                         throw new ParseException (location, "Invalid tag for application file.");
1167                         }
1168
1169                         ControlBuilder parent = stack.Builder;
1170                         ControlBuilder builder = null;
1171                         if (parent != null && parent.ControlType == typeof (HtmlTable) &&
1172                             (String.Compare (tagid, "thead", true, CultureInfo.InvariantCulture) == 0 ||
1173                              String.Compare (tagid, "tbody", true, CultureInfo.InvariantCulture) == 0)) {
1174                                 ignored = true;
1175                                 return true;
1176                         }
1177                                 
1178                         Hashtable htable = (atts != null) ? atts.GetDictionary (null) : emptyHash;
1179                         if (stack.Count > 1) {
1180                                 try {
1181                                         builder = parent.CreateSubBuilder (tagid, htable, null, tparser, location);
1182                                 } catch (TypeLoadException e) {
1183                                         throw new ParseException (Location, "Type not found.", e);
1184                                 } catch (Exception e) {
1185                                         throw new ParseException (Location, e.Message, e);
1186                                 }
1187                         }
1188
1189                         bool runatServer = atts != null && atts.IsRunAtServer ();
1190                         if (builder == null && runatServer) {
1191                                 string id = htable ["id"] as string;
1192                                 if (id != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (id))
1193                                         throw new ParseException (Location, "'" + id + "' is not a valid identifier");
1194                                         
1195                                 try {
1196                                         builder = RootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location);
1197                                 } catch (TypeLoadException e) {
1198                                         throw new ParseException (Location, "Type not found.", e);
1199                                 } catch (HttpException e) {
1200                                         CompilationException inner = e.InnerException as CompilationException;
1201                                         if (inner != null)
1202                                                 throw inner;
1203                                         
1204                                         throw new ParseException (Location, e.Message, e);
1205                                 } catch (Exception e) {
1206                                         throw new ParseException (Location, e.Message, e);
1207                                 }
1208                         }
1209                         
1210                         if (builder == null)
1211                                 return false;
1212
1213                         // This is as good as we can do for now - if the parsed location contains
1214                         // both expressions and code render blocks then we're out of luck...
1215                         string plainText = location.PlainText;
1216                         if (!runatServer && plainText.IndexOf ("<%$") == -1&& plainText.IndexOf ("<%") > -1)
1217                                 return false;
1218 #if NET_2_0
1219                         PageParserFilter pfilter = PageParserFilter;
1220                         if (pfilter != null && !pfilter.AllowControl (builder.ControlType, builder))
1221                                 throw new ParseException (Location, "Control type '" + builder.ControlType + "' not allowed.");
1222                         
1223                         if (!OtherControlsAllowed (builder))
1224                                 throw new ParseException (Location, "Only Content controls are allowed directly in a content page that contains Content controls.");
1225 #endif
1226                         
1227                         builder.Location = location;
1228                         builder.ID = htable ["id"] as string;
1229                         if (typeof (HtmlForm).IsAssignableFrom (builder.ControlType)) {
1230                                 if (inForm)
1231                                         throw new ParseException (location, "Only one <form> allowed.");
1232
1233                                 inForm = true;
1234                         }
1235
1236                         if (builder.HasBody () && !(builder is ObjectTagBuilder)) {
1237                                 if (builder is TemplateBuilder) {
1238                                 //      push the id list
1239                                 }
1240                                 stack.Push (builder, location);
1241                         } else {
1242                                 if (!isApplication && builder is ObjectTagBuilder) {
1243                                         ObjectTagBuilder ot = (ObjectTagBuilder) builder;
1244                                         if (ot.Scope != null && ot.Scope.Length > 0)
1245                                                 throw new ParseException (location, "Scope not allowed here");
1246
1247                                         if (tagtype == TagType.Tag) {
1248                                                 stack.Push (builder, location);
1249                                                 return true;
1250                                         }
1251                                 }
1252                                 
1253                                 parent.AppendSubBuilder (builder);
1254                                 builder.CloseControl ();
1255                         }
1256
1257                         return true;
1258                 }
1259
1260                 string ReadFile (string filename)
1261                 {
1262                         string realpath = tparser.MapPath (filename);
1263                         using (StreamReader sr = new StreamReader (realpath, WebEncoding.FileEncoding)) {
1264                                 string content = sr.ReadToEnd ();
1265                                 return content;
1266                         }
1267                 }
1268
1269                 bool ProcessScript (TagType tagtype, TagAttributes attributes)
1270                 {
1271                         if (tagtype != TagType.Close) {
1272                                 if (attributes != null && attributes.IsRunAtServer ()) {
1273                                         string language = (string) attributes ["language"];
1274                                         if (language != null && language.Length > 0 && tparser.ImplicitLanguage)
1275                                                 tparser.SetLanguage (language);
1276                                         CheckLanguage (language);
1277                                         string src = (string) attributes ["src"];
1278                                         if (src != null) {
1279                                                 if (src == "")
1280                                                         throw new ParseException (Parser,
1281                                                                 "src cannot be an empty string");
1282
1283                                                 string content = ReadFile (src);
1284                                                 inScript = true;
1285                                                 TextParsed (Parser, content);
1286                                                 FlushText ();
1287                                                 inScript = false;
1288                                                 if (tagtype != TagType.SelfClosing) {
1289                                                         ignore_text = true;
1290                                                         Parser.VerbatimID = "script";
1291                                                 }
1292                                         } else if (tagtype == TagType.Tag) {
1293                                                 Parser.VerbatimID = "script";
1294                                                 inScript = true;
1295                                         }
1296
1297                                         return true;
1298                                 } else {
1299                                         if (tagtype != TagType.SelfClosing) {
1300                                                 Parser.VerbatimID = "script";
1301                                                 javascript = true;
1302                                         }
1303                                         TextParsed (location, location.PlainText);
1304                                         return true;
1305                                 }
1306                         }
1307
1308                         bool result;
1309                         if (inScript) {
1310                                 result = inScript;
1311                                 inScript = false;
1312                         } else if (!ignore_text) {
1313                                 result = javascript;
1314                                 javascript = false;
1315                                 TextParsed (location, location.PlainText);
1316                         } else {
1317                                 ignore_text = false;
1318                                 result = true;
1319                         }
1320
1321                         return result;
1322                 }
1323
1324                 bool CloseControl (string tagid)
1325                 {
1326                         ControlBuilder current = stack.Builder;
1327                         string btag = current.OriginalTagName;
1328                         if (String.Compare (btag, "tbody", true, CultureInfo.InvariantCulture) != 0 &&
1329                             String.Compare (tagid, "tbody", true, CultureInfo.InvariantCulture) == 0) {
1330                                 if (!current.ChildrenAsProperties) {
1331                                         try {
1332                                                 TextParsed (location, location.PlainText);
1333                                                 FlushText ();
1334                                         } catch {}
1335                                 }
1336                                 return true;
1337                         }
1338
1339                         if (current.ControlType == typeof (HtmlTable) && String.Compare (tagid, "thead", true, CultureInfo.InvariantCulture) == 0)
1340                                 return true;
1341                         
1342                         if (0 != String.Compare (tagid, btag, true, CultureInfo.InvariantCulture))
1343                                 return false;
1344
1345                         // if (current is TemplateBuilder)
1346                         //      pop from the id list
1347                         if (current.NeedsTagInnerText ()) {
1348                                 try { 
1349                                         current.SetTagInnerText (tagInnerText.ToString ());
1350                                 } catch (Exception e) {
1351                                         throw new ParseException (current.Location, e.Message, e);
1352                                 }
1353
1354                                 tagInnerText.Length = 0;
1355                         }
1356
1357                         if (typeof (HtmlForm).IsAssignableFrom (current.ControlType)) {
1358                                 inForm = false;
1359                         }
1360
1361                         current.CloseControl ();
1362                         stack.Pop ();
1363                         stack.Builder.AppendSubBuilder (current);
1364                         return true;
1365                 }
1366
1367 #if NET_2_0
1368                 CodeConstructType MapTagTypeToConstructType (TagType tagtype)
1369                 {
1370                         switch (tagtype) {
1371                                 case TagType.CodeRenderExpression:
1372                                         return CodeConstructType.ExpressionSnippet;
1373
1374                                 case TagType.CodeRender:
1375                                         return CodeConstructType.CodeSnippet;
1376
1377                                 case TagType.DataBinding:
1378                                         return CodeConstructType.DataBindingSnippet;
1379
1380                                 default:
1381                                         throw new InvalidOperationException ("Unexpected tag type.");
1382                         }
1383                 }
1384                 
1385 #endif
1386                 bool ProcessCode (TagType tagtype, string code, ILocation location)
1387                 {
1388 #if NET_2_0
1389                         PageParserFilter pfilter = PageParserFilter;
1390                         // LAMESPEC:
1391                         //
1392                         // http://msdn.microsoft.com/en-us/library/system.web.ui.pageparserfilter.processcodeconstruct.aspx
1393                         //
1394                         // The above page says if false is returned then we should NOT process the
1395                         // code further, wheras in reality it's the other way around. The
1396                         // ProcessCodeConstruct return value means whether or not the filter
1397                         // _processed_ the code.
1398                         //
1399                         if (pfilter != null && (!pfilter.AllowCode || pfilter.ProcessCodeConstruct (MapTagTypeToConstructType (tagtype), code)))
1400                                 return true;
1401 #endif
1402                         ControlBuilder b = null;
1403                         if (tagtype == TagType.CodeRender)
1404                                 b = new CodeRenderBuilder (code, false, location);
1405                         else if (tagtype == TagType.CodeRenderExpression)
1406                                 b = new CodeRenderBuilder (code, true, location);
1407                         else if (tagtype == TagType.DataBinding)
1408                                 b = new DataBindingBuilder (code, location);
1409                         else
1410                                 throw new HttpException ("Should never happen");
1411
1412                         stack.Builder.AppendSubBuilder (b);
1413                         return true;
1414                 }
1415
1416                 public ILocation Location {
1417                         get { return location; }
1418                 }
1419
1420                 void CheckLanguage (string lang)
1421                 {
1422                         if (lang == null || lang == "")
1423                                 return;
1424
1425                         if (String.Compare (lang, tparser.Language, true, CultureInfo.InvariantCulture) == 0)
1426                                 return;
1427
1428 #if NET_2_0
1429                         CompilationSection section = (CompilationSection) WebConfigurationManager.GetWebApplicationSection ("system.web/compilation");
1430                         if (section.Compilers[tparser.Language] != section.Compilers[lang])
1431 #else
1432                         CompilationConfiguration cfg = CompilationConfiguration.GetInstance (HttpContext.Current); 
1433                         if (!cfg.Compilers.CompareLanguages (tparser.Language, lang))
1434 #endif
1435                                 throw new ParseException (Location,
1436                                                 String.Format ("Trying to mix language '{0}' and '{1}'.", 
1437                                                                 tparser.Language, lang));
1438                 }
1439
1440                 // Used to get CodeRender tags in attribute values
1441                 class CodeRenderParser
1442                 {
1443                         string str;
1444                         ControlBuilder builder;
1445                         AspGenerator generator;
1446                         
1447                         public CodeRenderParser (string str, ControlBuilder builder)
1448                         {
1449                                 this.str = str;
1450                                 this.builder = builder;
1451                         }
1452
1453                         public void AddChildren (AspGenerator generator)
1454                         {
1455                                 this.generator = generator;
1456                                 int index = str.IndexOf ("<%");
1457                                 if (index > 0)
1458                                         DoParseExpressions (str);
1459                                 else
1460                                         DoParse (str);
1461                         }
1462
1463                         void DoParseExpressions (string str)
1464                         {
1465                                 int startIndex = 0, index = 0;
1466                                 Regex codeDirective = new Regex ("(<%(?!@)(?<code>.*?)%>)|(<[\\w:\\.]+.*?runat=[\"']?server[\"']?.*?/>)",
1467                                                                  RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.CultureInvariant);
1468                                 Match match;
1469                                 int strLen = str.Length;
1470                                 
1471                                 while (index > -1 && startIndex < strLen) {
1472                                         match = codeDirective.Match (str, index);
1473                                         
1474                                         if (match.Success) {
1475                                                 string value = match.Value;
1476                                                 index = match.Index;
1477                                                 if (index > startIndex)
1478                                                         TextParsed (null, str.Substring (startIndex, index - startIndex));
1479                                                 DoParse (value);
1480                                                 index += value.Length;
1481                                                 startIndex = index;
1482                                         } else
1483                                                 break;
1484
1485                                         if (index < strLen)
1486                                                 index = str.IndexOf ('<', index);
1487                                         else
1488                                                 break;
1489                                 }
1490                                 
1491                                 if (startIndex < strLen)
1492                                         TextParsed (null, str.Substring (startIndex));
1493                         }
1494                         
1495                         void DoParse (string str)
1496                         {
1497                                 AspParser parser = new AspParser ("@@nested_tag@@", new StringReader (str));
1498                                 parser.Error += new ParseErrorHandler (ParseError);
1499                                 parser.TagParsed += new TagParsedHandler (TagParsed);
1500                                 parser.TextParsed += new TextParsedHandler (TextParsed);
1501                                 parser.Parse ();
1502                         }
1503
1504                         void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
1505                         {
1506                                 switch (tagtype) {
1507                                         case TagType.CodeRender:
1508                                                 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location));
1509                                                 break;
1510                                                 
1511                                         case TagType.CodeRenderExpression:
1512                                                 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location));
1513                                                 break;
1514                                                 
1515                                         case TagType.DataBinding:
1516                                                 builder.AppendSubBuilder (new DataBindingBuilder (tagid, location));
1517                                                 break;
1518
1519                                         case TagType.Tag:
1520                                         case TagType.SelfClosing:
1521                                         case TagType.Close:
1522                                                 if (generator != null)
1523                                                         generator.TagParsed (location, tagtype, tagid, attributes);
1524                                                 else
1525                                                         goto default;
1526                                                 break;
1527                                                 
1528                                         default:
1529                                                 string text = location.PlainText;
1530                                                 if (text != null && text.Trim ().Length > 0)
1531                                                         builder.AppendLiteralString (text);
1532                                                 break;
1533                                 }
1534                         }
1535
1536                         void TextParsed (ILocation location, string text)
1537                         {
1538                                 builder.AppendLiteralString (text);
1539                         }
1540
1541                         void ParseError (ILocation location, string message)
1542                         {
1543                                 throw new ParseException (location, message);
1544                         }
1545                 }
1546         }
1547 }
1548