2009-12-14 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.Compilation / AspGenerator.cs
index 0999c3dfddf47498fe408ef493891bc367a345b2..ed162cafeae522d7146a66b7a35e154bec9816f7 100644 (file)
@@ -3,9 +3,10 @@
 //
 // Authors:
 //     Gonzalo Paniagua Javier (gonzalo@ximian.com)
+//      Marek Habersack <mhabersack@novell.com>
 //
 // (C) 2002,2003 Ximian, Inc (http://www.ximian.com)
-// Copyright (c) 2004,2006 Novell, Inc (http://www.novell.com)
+// Copyright (c) 2004-2009 Novell, Inc (http://www.novell.com)
 //
 
 //
 //
 using System;
 using System.Collections;
+using System.Collections.Generic;
 using System.CodeDom.Compiler;
 using System.Globalization;
 using System.IO;
 using System.Text;
+using System.Text.RegularExpressions;
 using System.Web.Caching;
 using System.Web.Configuration;
+using System.Web.Hosting;
 using System.Web.UI;
 using System.Web.UI.HtmlControls;
 using System.Web.Util;
@@ -50,7 +54,7 @@ namespace System.Web.Compilation
                public BuilderLocation (ControlBuilder builder, ILocation location)
                {
                        this.Builder = builder;
-                       this.Location = location;
+                       this.Location = new Location (location);
                }
        }
 
@@ -163,7 +167,7 @@ namespace System.Web.Compilation
                        if (tags.Count == 0)
                                return false;
 
-                       return 0 == String.Compare (tagid, (string) tags.Peek (), true, CultureInfo.InvariantCulture);
+                       return 0 == String.Compare (tagid, (string) tags.Peek (), true, Helpers.InvariantCulture);
                }
                
                public int Count {
@@ -175,8 +179,58 @@ namespace System.Web.Compilation
                }
        }
 
+       enum TextBlockType
+       {
+               Verbatim,
+               Expression,
+               Tag,
+               Comment
+       }
+       
+       sealed class TextBlock
+       {
+               public string Content;
+               public readonly TextBlockType Type;
+               public readonly int Length;
+               
+               public TextBlock (TextBlockType type, string content)
+               {
+                       Content = content;
+                       Type = type;
+                       Length = content.Length;
+               }
+
+               public override string ToString ()
+               {
+                       return this.GetType ().FullName + " [" + this.Type + "]";
+               }
+       }
+       
        class AspGenerator
        {
+               const int READ_BUFFER_SIZE = 8192;
+               
+               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);
+
+               static readonly Regex runatServer = new Regex (@"<[\w:\.]+.*?runat=[""']?server[""']?.*?/?>",
+                                                              RegexOptions.Compiled | RegexOptions.Singleline |
+                                                              RegexOptions.Multiline | RegexOptions.IgnoreCase |
+                                                              RegexOptions.CultureInvariant);
+               
+               static readonly Regex endOfTag = new Regex (@"</[\w:\.]+\s*?>",
+                                                           RegexOptions.Compiled | RegexOptions.Singleline |
+                                                           RegexOptions.Multiline | RegexOptions.IgnoreCase |
+                                                           RegexOptions.CultureInvariant);
+               
+               static readonly Regex expressionRegex = new Regex (@"<%.*?%>",
+                                                                  RegexOptions.Compiled | RegexOptions.Singleline |
+                                                                  RegexOptions.Multiline | RegexOptions.IgnoreCase |
+                                                                  RegexOptions.CultureInvariant);
+
+               static readonly Regex clientCommentRegex = new Regex (@"<!--(.|\s)*?-->",
+                                                                     RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase |
+                                                                     RegexOptions.CultureInvariant);
+               
                ParserStack pstack;
                BuilderLocationStack stack;
                TemplateParser tparser;
@@ -189,20 +243,25 @@ namespace System.Web.Compilation
                static Hashtable emptyHash = new Hashtable ();
                bool inForm;
                bool useOtherTags;
+               TagType lastTag;
+               AspComponentFoundry componentFoundry;
+               Stream inputStream;
 
+               public AspGenerator (TemplateParser tparser, AspComponentFoundry componentFoundry) : this (tparser)
+               {
+                       this.componentFoundry = componentFoundry;
+               }
+               
                public AspGenerator (TemplateParser tparser)
                {
                        this.tparser = tparser;
                        text = new StringBuilder ();
                        stack = new BuilderLocationStack ();
-                       rootBuilder = new RootBuilder (tparser);
-                       stack.Push (rootBuilder, null);
-                       tparser.RootBuilder = rootBuilder;
                        pstack = new ParserStack ();
                }
 
                public RootBuilder RootBuilder {
-                       get { return tparser.RootBuilder; }
+                       get { return rootBuilder; }
                }
 
                public AspParser Parser {
@@ -212,6 +271,245 @@ namespace System.Web.Compilation
                public string Filename {
                        get { return pstack.Filename; }
                }
+
+               PageParserFilter PageParserFilter {
+                       get {
+                               if (tparser == null)
+                                       return null;
+
+                               return tparser.PageParserFilter;
+                       }
+               }
+
+               // KLUDGE WARNING
+               //
+               // The kludge to determine the base type of the to-be-generated ASP.NET class is
+               // very unfortunate but with our current parser it is, unfortunately, necessary. The
+               // reason for reading the entire file into memory and parsing it with a regexp is
+               // that we need to read the main directive (i.e. <%@Page %>, <%@Control %> etc),
+               // pass it to the page parser filter if it exists, and finally read the inherits
+               // attribute of the directive to get access to the base type of the class to be
+               // generated. On that type we check whether it is decorated with the
+               // FileLevelControlBuilder attribute and, if yes, use the indicated type as the
+               // RootBuilder. This is necessary for the ASP.NET MVC views using the "generic"
+               // inherits declaration to work properly. Our current parser is not able to parse
+               // the input file out of sequence (i.e. directives first, then the rest) so we need
+               // to do what we do below, alas.
+               Hashtable GetDirectiveAttributesDictionary (string skipKeyName, CaptureCollection names, CaptureCollection values)
+               {
+                       var ret = new Hashtable (StringComparer.InvariantCultureIgnoreCase);
+
+                       int index = 0;
+                       string keyName;
+                       foreach (Capture c in names) {
+                               keyName = c.Value;
+                               if (String.Compare (skipKeyName, keyName, StringComparison.OrdinalIgnoreCase) == 0) {
+                                       index++;
+                                       continue;
+                               }
+                               
+                               ret.Add (c.Value, values [index++].Value);
+                       }
+
+                       return ret;
+               }
+
+               string GetDirectiveName (CaptureCollection names)
+               {
+                       string val;
+                       foreach (Capture c in names) {
+                               val = c.Value;
+                               if (Directive.IsDirective (val))
+                                       return val;
+                       }
+
+                       return tparser.DefaultDirectiveName;
+               }
+
+               int GetLineNumberForIndex (string fileContents, int index)
+               {
+                       int line = 1;
+                       char c;
+                       bool foundCR = false;
+                       
+                       for (int pos = 0; pos < index; pos++) {
+                               c = fileContents [pos];
+                               if (c == '\n' || foundCR) {
+                                       line++;
+                                       foundCR = false;
+                               }
+                               
+                               foundCR = (c == '\r');
+                       }
+
+                       return line;
+               }
+
+               int GetNumberOfLinesForRange (string fileContents, int index, int length)
+               {
+                       int lines = 0;
+                       int stop = index + length;
+                       char c;
+                       bool foundCR = false;
+                       
+                       for (int pos = index; pos < stop; pos++) {
+                               c = fileContents [pos];
+                               if (c == '\n' || foundCR) {
+                                       lines++;
+                                       foundCR = false;
+                               }
+
+                               foundCR = (c == '\r');
+                       }
+
+                       return lines;
+               }
+               
+               Type GetInheritedType (string fileContents, string filename)
+               {
+                       MatchCollection matches = DirectiveRegex.Matches (fileContents);
+                       if (matches == null || matches.Count == 0)
+                               return null;
+
+                       string wantedDirectiveName = tparser.DefaultDirectiveName.ToLower (Helpers.InvariantCulture);
+                       string directiveName;
+                       GroupCollection groups;
+                       CaptureCollection ccNames;
+                       
+                       foreach (Match match in matches) {
+                               groups = match.Groups;
+                               if (groups.Count < 6)
+                                       continue;
+
+                               ccNames = groups [3].Captures;
+                               directiveName = GetDirectiveName (ccNames);
+                               if (String.IsNullOrEmpty (directiveName))
+                                       continue;
+                               
+                               if (String.Compare (directiveName.ToLower (Helpers.InvariantCulture), wantedDirectiveName, StringComparison.Ordinal) != 0)
+                                       continue;
+
+                               var loc = new Location (null);
+                               int index = match.Index;
+                               
+                               loc.Filename = filename;
+                               loc.BeginLine = GetLineNumberForIndex (fileContents, index);
+                               loc.EndLine = loc.BeginLine + GetNumberOfLinesForRange (fileContents, index, match.Length);
+                               
+                               tparser.Location = loc;
+                               tparser.allowedMainDirectives = 2;
+                               tparser.AddDirective (wantedDirectiveName, GetDirectiveAttributesDictionary (wantedDirectiveName, ccNames, groups [5].Captures));
+
+                               return tparser.BaseType;
+                       }
+                       
+                       return null;
+               }
+
+               string ReadFileContents (Stream inputStream, string filename)
+               {
+                       string ret = null;
+                       
+                       if (inputStream != null) {
+                               if (inputStream.CanSeek) {
+                                       long curPos = inputStream.Position;
+                                       inputStream.Seek (0, SeekOrigin.Begin);
+
+                                       Encoding enc = WebEncoding.FileEncoding;
+                                       StringBuilder sb = new StringBuilder ();
+                                       byte[] buffer = new byte [READ_BUFFER_SIZE];
+                                       int nbytes;
+                                       
+                                       while ((nbytes = inputStream.Read (buffer, 0, READ_BUFFER_SIZE)) > 0)
+                                               sb.Append (enc.GetString (buffer, 0, nbytes));
+                                       inputStream.Seek (curPos, SeekOrigin.Begin);
+                                       
+                                       ret = sb.ToString ();
+                                       sb.Length = 0;
+                                       sb.Capacity = 0;
+                               } else {
+                                       FileStream fs = inputStream as FileStream;
+                                       if (fs != null) {
+                                               string fname = fs.Name;
+                                               try {
+                                                       if (File.Exists (fname))
+                                                               ret = File.ReadAllText (fname);
+                                               } catch {
+                                                       // ignore
+                                               }
+                                       }
+                               }
+                       }
+
+                       if (ret == null && !String.IsNullOrEmpty (filename) && String.Compare (filename, "@@inner_string@@", StringComparison.Ordinal) != 0) {
+                               try {
+                                       if (File.Exists (filename))
+                                               ret = File.ReadAllText (filename);
+                               } catch {
+                                       // ignore
+                               }
+                       }
+
+                       return ret;
+               }
+               
+               Type GetRootBuilderType (Stream inputStream, string filename)
+               {
+                       Type ret = null;
+                       string fileContents;
+
+                       if (tparser != null)
+                               fileContents = ReadFileContents (inputStream, filename);
+                       else
+                               fileContents = null;
+                       
+                       if (!String.IsNullOrEmpty (fileContents)) {
+                               Type inheritedType = GetInheritedType (fileContents, filename);
+                               fileContents = null;
+                               if (inheritedType != null) {
+                                       FileLevelControlBuilderAttribute attr;
+                                       
+                                       try {
+                                               object[] attrs = inheritedType.GetCustomAttributes (typeof (FileLevelControlBuilderAttribute), true);
+                                               if (attrs != null && attrs.Length > 0)
+                                                       attr = attrs [0] as FileLevelControlBuilderAttribute;
+                                               else
+                                                       attr = null;
+                                       } catch {
+                                               attr = null;
+                                       }
+
+                                       ret = attr != null ? attr.BuilderType : null;
+                               }
+                       }
+                       
+                       if (ret == null) {
+                               if (tparser is PageParser)
+                                       return typeof (FileLevelPageControlBuilder);
+                               else if (tparser is UserControlParser)
+                                       return typeof (FileLevelUserControlBuilder);
+                               else
+                                       return typeof (RootBuilder);
+                       } else
+                               return ret;
+               }
+               
+               void CreateRootBuilder (Stream inputStream, string filename)
+               {
+                       if (rootBuilder != null)
+                               return;
+                       
+                       Type rootBuilderType = GetRootBuilderType (inputStream, filename);
+                       rootBuilder = Activator.CreateInstance (rootBuilderType) as RootBuilder;
+                       if (rootBuilder == null)
+                               throw new HttpException ("Cannot create an instance of file-level control builder.");
+                       rootBuilder.Init (tparser, null, null, null, null, null);
+                       if (componentFoundry != null)
+                               rootBuilder.Foundry = componentFoundry;
+                       
+                       stack.Push (rootBuilder, null);
+                       tparser.RootBuilder = rootBuilder;
+               }
                
                BaseCompiler GetCompilerFromType ()
                {
@@ -224,71 +522,196 @@ namespace System.Web.Compilation
 
                        if (type == typeof (UserControlParser))
                                return new UserControlCompiler ((UserControlParser) tparser);
-#if NET_2_0
+
                        if (type == typeof(MasterPageParser))
                                return new MasterPageCompiler ((MasterPageParser) tparser);
-#endif
 
                        throw new Exception ("Got type: " + type);
                }
-               
-               void InitParser (string filename)
+
+               void InitParser (TextReader reader, string filename)
                {
-                       StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
                        AspParser parser = new AspParser (filename, reader);
-                       reader.Close ();
                        parser.Error += new ParseErrorHandler (ParseError);
                        parser.TagParsed += new TagParsedHandler (TagParsed);
                        parser.TextParsed += new TextParsedHandler (TextParsed);
+                       parser.ParsingComplete += new ParsingCompleteHandler (ParsingCompleted);
+                       tparser.AspGenerator = this;
+                       CreateRootBuilder (inputStream, filename);
+
                        if (!pstack.Push (parser))
                                throw new ParseException (Location, "Infinite recursion detected including file: " + filename);
-                       tparser.AddDependency (filename);
+
+                       if (filename != "@@inner_string@@") {
+                               string arvp = Path.Combine (tparser.BaseVirtualDir, Path.GetFileName (filename));
+                               if (VirtualPathUtility.IsAbsolute (arvp))
+                                       arvp = VirtualPathUtility.ToAppRelative (arvp);
+                               
+                               tparser.AddDependency (arvp);
+                       }
                }
+               
+               void InitParser (string filename)
+               {
+                       StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
+                       InitParser (reader, filename);
+               }
+
+               void CheckForDuplicateIds (ControlBuilder root, Stack scopes)
+               {
+                       if (root == null)
+                               return;
+                       
+                       if (scopes == null)
+                               scopes = new Stack ();                  
+
+                       Dictionary <string, bool> ids;
+                       
+                       if (scopes.Count == 0 || root.IsNamingContainer) {
+                               ids = new Dictionary <string, bool> (StringComparer.Ordinal);
+                               scopes.Push (ids);
+                       } else {
+                               ids = scopes.Peek () as Dictionary <string, bool>;
+                       }
+                       
+                       if (ids == null)
+                               return;
 
+                       ControlBuilder cb;
+                       string id;
+                       ArrayList children = root.Children;
+                       if (children != null) {
+                               foreach (object o in children) {
+                                       cb = o as ControlBuilder;
+                                       if (cb == null)
+                                               continue;
+
+                                       id = cb.ID;
+                                       if (id == null || id.Length == 0)
+                                               continue;
+                               
+                                       if (ids.ContainsKey (id))
+                                               throw new ParseException (cb.Location, "Id '" + id + "' is already used by another control.");
+
+                                       ids.Add (id, true);
+                                       CheckForDuplicateIds (cb, scopes);
+                               }
+                       }
+               }
+               
                public void Parse (string file)
                {
-                       InitParser (file);
+                       Parse (file, false);
+               }
+               
+               public void Parse (TextReader reader, string filename, bool doInitParser)
+               {
+                       try {
+                               isApplication = tparser.DefaultDirectiveName == "application";
 
-                       pstack.Parser.Parse ();
-                       if (text.Length > 0)
-                               FlushText ();
+                               if (doInitParser)
+                                       InitParser (reader, filename);
 
-                       pstack.Pop ();
+                               pstack.Parser.Parse ();
+                               if (text.Length > 0)
+                                       FlushText ();
+
+                               tparser.MD5Checksum = pstack.Parser.MD5Checksum;
+                               pstack.Pop ();
 
 #if DEBUG
-                       PrintTree (rootBuilder, 0);
+                               PrintTree (RootBuilder, 0);
 #endif
 
-                       if (stack.Count > 1 && pstack.Count == 0)
-                               throw new ParseException (stack.Builder.location,
-                                               "Expecting </" + stack.Builder.TagName + "> " + stack.Builder);
+                               if (stack.Count > 1 && pstack.Count == 0)
+                                       throw new ParseException (stack.Builder.Location,
+                                                                 "Expecting </" + stack.Builder.TagName + "> " + stack.Builder);
+
+                               CheckForDuplicateIds (RootBuilder, null);
+                       } finally {
+                               if (reader != null)
+                                       reader.Close ();
+                       }
+               }
+
+               public void Parse (Stream stream, string filename, bool doInitParser)
+               {
+                       inputStream = stream;
+                       Parse (new StreamReader (stream, WebEncoding.FileEncoding), filename, doInitParser);
+               }
+               
+               public void Parse (string filename, bool doInitParser)
+               {
+                       StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
+                       Parse (reader, filename, doInitParser);
                }
 
                public void Parse ()
                {
-                       Parse (Path.GetFullPath (tparser.InputFile));
+                       string inputFile = tparser.InputFile;
+                       TextReader inputReader = tparser.Reader;
+
+                       try {                   
+                               if (String.IsNullOrEmpty (inputFile)) {
+                                       StreamReader sr = inputReader as StreamReader;
+                                       if (sr != null) {
+                                               FileStream fr = sr.BaseStream as FileStream;
+                                               if (fr != null)
+                                                       inputFile = fr.Name;
+                                       }
+
+                                       if (String.IsNullOrEmpty (inputFile))
+                                               inputFile = "@@inner_string@@";
+                               }
+
+                               if (inputReader != null) {
+                                       Parse (inputReader, inputFile, true);
+                               } else {
+                                       if (String.IsNullOrEmpty (inputFile))
+                                               throw new HttpException ("Parser input file is empty, cannot continue.");
+                                       inputFile = Path.GetFullPath (inputFile);
+                                       InitParser (inputFile);
+                                       Parse (inputFile);
+                               }
+                       } finally {
+                               if (inputReader != null)
+                                       inputReader.Close ();
+                       }
                }
 
+               internal static void AddTypeToCache (ArrayList dependencies, string inputFile, Type type)
+               {
+                       if (type == null || inputFile == null || inputFile.Length == 0)
+                               return;
+
+                       if (dependencies != null && dependencies.Count > 0) {
+                               string [] deps = (string []) dependencies.ToArray (typeof (string));
+                               HttpContext ctx = HttpContext.Current;
+                               HttpRequest req = ctx != null ? ctx.Request : null;
+                               
+                               if (req == null)
+                                       throw new HttpException ("No current context, cannot compile.");
+
+                               for (int i = 0; i < deps.Length; i++)
+                                       deps [i] = req.MapPath (deps [i]);
+
+                               HttpRuntime.InternalCache.Insert ("@@Type" + inputFile, type, new CacheDependency (deps));
+                       } else
+                               HttpRuntime.InternalCache.Insert ("@@Type" + inputFile, type);
+               }
+               
                public Type GetCompiledType ()
                {
-                       Type type = (Type) HttpRuntime.Cache.Get ("@@Type" + tparser.InputFile);
+                       Type type = (Type) HttpRuntime.InternalCache.Get ("@@Type" + tparser.InputFile);
                        if (type != null) {
                                return type;
                        }
 
-                       isApplication = tparser.DefaultDirectiveName == "application";
-#if NET_2_0
-                       tparser.RegisterConfigControls ();
-#endif
                        Parse ();
-
                        BaseCompiler compiler = GetCompilerFromType ();
-
+                       
                        type = compiler.GetCompiledType ();
-                       CacheDependency cd = new CacheDependency ((string[])
-                                                       tparser.Dependencies.ToArray (typeof (string)));
-
-                       HttpRuntime.Cache.InsertPrivate ("@@Type" + tparser.InputFile, type, cd);
+                       AddTypeToCache (tparser.Dependencies, tparser.InputFile, type);
                        return type;
                }
 
@@ -300,8 +723,8 @@ namespace System.Web.Compilation
 
                        string i = new string ('\t', indent);
                        Console.Write (i);
-                       Console.WriteLine ("b: {0} id: {1} type: {2} parent: {3}",
-                                          builder, builder.ID, builder.ControlType, builder.parentBuilder);
+                       Console.WriteLine ("b: {0}; naming container: {1}; id: {2}; type: {3}; parent: {4}",
+                                          builder, builder.IsNamingContainer, builder.ID, builder.ControlType, builder.ParentBuilder);
 
                        if (builder.Children != null)
                        foreach (object o in builder.Children) {
@@ -327,33 +750,165 @@ namespace System.Web.Compilation
                        throw new ParseException (location, message);
                }
 
+               // KLUDGE WARNING!!
+               //
+               // The code below (ProcessTagsInAttributes, ParseAttributeTag) serves the purpose to work
+               // around a limitation of the current asp.net parser which is unable to parse server
+               // controls inside client tag attributes. Since the architecture of the current
+               // parser does not allow for clean solution of this problem, hence the kludge
+               // below. It will be gone as soon as the parser is rewritten.
+               //
+               // The kludge supports only self-closing tags inside attributes.
+               //
+               // KLUDGE WARNING!!
+               bool ProcessTagsInAttributes (ILocation location, string tagid, TagAttributes attributes, TagType type)
+               {
+                       if (attributes == null || attributes.Count == 0)
+                               return false;
+                       
+                       Match match;
+                       Group group;
+                       string value;
+                       bool retval = false;
+                       int index, length;
+                       StringBuilder sb = new StringBuilder ();
+
+                       sb.AppendFormat ("\t<{0}", tagid);
+                       foreach (string key in attributes.Keys) {
+                               value = attributes [key] as string;
+                               if (value == null || value.Length < 16) { // optimization
+                                       sb.AppendFormat (" {0}=\"{1}\"", key, value);
+                                       continue;
+                               }
+                               
+                               match = runatServer.Match (attributes [key] as string);
+                               if (!match.Success) {
+                                       sb.AppendFormat (" {0}=\"{1}\"", key, value);
+                                       continue;
+                               }
+                               if (sb.Length > 0) {
+                                       TextParsed (location, sb.ToString ());
+                                       sb.Length = 0;
+                               }
+                               
+                               retval = true;
+                               group = match.Groups [0];
+                               index = group.Index;
+                               length = group.Length;
+
+                               TextParsed (location, String.Format (" {0}=\"{1}", key, index > 0 ? value.Substring (0, index) : String.Empty));;
+                               FlushText ();
+                               ParseAttributeTag (group.Value, location);
+                               if (index + length < value.Length)
+                                       TextParsed (location, value.Substring (index + length) + "\"");
+                               else
+                                       TextParsed (location, "\"");
+                       }
+                       if (type == TagType.SelfClosing)
+                               sb.Append ("/>");
+                       else
+                               sb.Append (">");
+
+                       if (retval && sb.Length > 0)
+                               TextParsed (location, sb.ToString ());
+                       
+                       return retval;
+               }
+
+               void ParseAttributeTag (string code, ILocation location)
+               {
+                       AspParser outerParser = location as AspParser;
+                       int positionOffset = outerParser != null ? outerParser.BeginPosition : 0;
+                       AspParser parser = new AspParser ("@@attribute_tag@@", new StringReader (code), location.BeginLine - 1, positionOffset, outerParser);
+                       parser.Error += new ParseErrorHandler (ParseError);
+                       parser.TagParsed += new TagParsedHandler (TagParsed);
+                       parser.TextParsed += new TextParsedHandler (TextParsed);
+                       parser.Parse ();
+                       if (text.Length > 0)
+                               FlushText ();
+               }
+
+               void ParsingCompleted ()
+               {
+                       PageParserFilter pfilter = PageParserFilter;
+                       if (pfilter == null)
+                               return;
+
+                       pfilter.ParseComplete (RootBuilder);
+               }
+
+               void CheckIfIncludeFileIsSecure (string filePath)
+               {
+                       if (filePath == null || filePath.Length == 0)
+                               return;
+                       
+                       // a bit slow, but fully portable
+                       string newdir = null;
+                       Exception exception = null;
+                       try {
+                               string origdir = Directory.GetCurrentDirectory ();
+                               Directory.SetCurrentDirectory (Path.GetDirectoryName (filePath));
+                               newdir = Directory.GetCurrentDirectory ();
+                               Directory.SetCurrentDirectory (origdir);
+                               if (newdir [newdir.Length - 1] != '/')
+                                       newdir += "/";
+                       } catch (DirectoryNotFoundException) {
+                               return; // will be converted into 404
+                       } catch (FileNotFoundException) {
+                               return; // as above
+                       } catch (Exception ex) {
+                               // better safe than sorry
+                               exception = ex;
+                       }
+
+                       if (exception != null || !StrUtils.StartsWith (newdir, HttpRuntime.AppDomainAppPath))
+                               throw new ParseException (Location, "Files above the application's root directory cannot be included.");
+               }
+
+               string ChopOffTagStart (ILocation location, string content, string tagid)
+               {
+                       string tagstart = '<' + tagid;
+                       if (content.StartsWith (tagstart)) {
+                               TextParsed (location, tagstart);
+                               content = content.Substring (tagstart.Length);
+                       }
+
+                       return content;
+               }
+               
                void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
                {
+                       bool tagIgnored;
+                       
                        this.location = new Location (location);
                        if (tparser != null)
                                tparser.Location = location;
 
                        if (text.Length != 0)
-                               FlushText ();
+                               FlushText (lastTag == TagType.CodeRender);
 
-                       if (0 == String.Compare (tagid, "script", true, CultureInfo.InvariantCulture)) {
+                       if (0 == String.Compare (tagid, "script", true, Helpers.InvariantCulture)) {
                                bool in_script = (inScript || ignore_text);
-                               if (in_script || (tagtype != TagType.Close && attributes != null)) {
-                                       if ((in_script || attributes.IsRunAtServer ()) && ProcessScript (tagtype, attributes))
+                               if (in_script) {
+                                       if (ProcessScript (tagtype, attributes))
+                                               return;
+                               } else
+                                       if (ProcessScript (tagtype, attributes))
                                                return;
-                               }
                        }
 
+                       lastTag = tagtype;
                        switch (tagtype) {
                        case TagType.Directive:
-                               if (tagid == "")
+                               if (tagid.Length == 0)
                                        tagid = tparser.DefaultDirectiveName;
 
                                tparser.AddDirective (tagid, attributes.GetDictionary (null));
                                break;
                        case TagType.Tag:
-                               if (ProcessTag (tagid, attributes, tagtype)) {
-                                       useOtherTags = true;
+                               if (ProcessTag (location, tagid, attributes, tagtype, out tagIgnored)) {
+                                       if (!tagIgnored)
+                                               useOtherTags = true;
                                        break;
                                }
 
@@ -362,7 +917,11 @@ namespace System.Web.Compilation
                                        stack.Builder.OtherTags.Add (tagid);
                                }
 
-                               TextParsed (location, location.PlainText);
+                               {
+                                       string plainText = location.PlainText;
+                                       if (!ProcessTagsInAttributes (location, tagid, attributes, TagType.Tag))
+                                               TextParsed (location, ChopOffTagStart (location, plainText, tagid));
+                               }
                                break;
                        case TagType.Close:
                                bool notServer = (useOtherTags && TryRemoveTag (tagid, stack.Builder.OtherTags));
@@ -373,8 +932,10 @@ namespace System.Web.Compilation
                                break;
                        case TagType.SelfClosing:
                                int count = stack.Count;
-                               if (!ProcessTag (tagid, attributes, tagtype)) {
-                                       TextParsed (location, location.PlainText);
+                               if (!ProcessTag (location, tagid, attributes, tagtype, out tagIgnored) && !tagIgnored) {
+                                       string plainText = location.PlainText;
+                                       if (!ProcessTagsInAttributes (location, tagid, attributes, TagType.SelfClosing))
+                                               TextParsed (location, ChopOffTagStart (location, plainText, tagid));
                                } else if (stack.Count != count) {
                                        CloseControl (tagid);
                                }
@@ -399,12 +960,30 @@ namespace System.Web.Compilation
                                        file = attributes ["file"] as string;
 
                                if (isvirtual) {
-                                       file = tparser.MapPath (file);
+                                       bool parsed = false;
+                                       VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
+
+                                       if (vpp.FileExists (file)) {
+                                               VirtualFile vf = vpp.GetFile (file);
+                                               if (vf != null) {
+                                                       Parse (vf.Open (), file, true);
+                                                       parsed = true;
+                                               }
+                                       }
+                                       
+                                       if (!parsed)
+                                               Parse (tparser.MapPath (file), true);
                                } else {
-                                       file = GetIncludeFilePath (tparser.BaseDir, file);
+                                       string includeFilePath = GetIncludeFilePath (tparser.ParserDir, file);
+                                       CheckIfIncludeFileIsSecure (includeFilePath);
+                                       tparser.PushIncludeDir (Path.GetDirectoryName (includeFilePath));
+                                       try {
+                                               Parse (includeFilePath, true);
+                                       } finally {
+                                               tparser.PopIncludeDir ();
+                                       }
                                }
-
-                               Parse (file);
+                               
                                break;
                        default:
                                break;
@@ -419,7 +998,7 @@ namespace System.Web.Compilation
 
                        for (int idx = otags.Count - 1; idx >= 0; idx--) {
                                string otagid = (string) otags [idx];
-                               if (0 == String.Compare (tagid, otagid, true, CultureInfo.InvariantCulture)) {
+                               if (0 == String.Compare (tagid, otagid, true, Helpers.InvariantCulture)) {
                                        do {
                                                otags.RemoveAt (idx);
                                        } while (otags.Count - 1 >= idx);
@@ -436,31 +1015,143 @@ namespace System.Web.Compilation
 
                        return Path.GetFullPath (Path.Combine (basedir, filename));
                }
+
+               delegate bool CheckBlockEnd (string text);
                
+               bool CheckTagEndNeeded (string text)
+               {
+                       return !text.EndsWith ("/>");
+               }
+               
+               List <TextBlock> FindRegexBlocks (Regex rxStart, Regex rxEnd, CheckBlockEnd checkEnd, IList blocks, TextBlockType typeForMatches, bool discardBlocks)
+               {
+                       var ret = new List <TextBlock> ();
+                       foreach (TextBlock block in blocks) {
+                               if (block.Type != TextBlockType.Verbatim) {
+                                       ret.Add (block);
+                                       continue;
+                               }
+
+                               int lastIndex = 0, index;
+                               MatchCollection matches = rxStart.Matches (block.Content);
+                               bool foundMatches = matches.Count > 0;
+                               foreach (Match match in matches) {
+                                       foundMatches = true;
+                                       index = match.Index;
+                                       if (lastIndex < index)
+                                               ret.Add (new TextBlock (TextBlockType.Verbatim, block.Content.Substring (lastIndex, index - lastIndex)));
+
+                                       string value = match.Value;
+                                       if (rxEnd != null && checkEnd (value)) {
+                                               int startFrom = index + value.Length;
+                                               Match m = rxEnd.Match (block.Content, startFrom);
+                                               if (m.Success)
+                                                       value += block.Content.Substring (startFrom, m.Index - startFrom) + m.Value;
+                                       }
+
+                                       if (!discardBlocks)
+                                               ret.Add (new TextBlock (typeForMatches, value));
+                                       lastIndex = index + value.Length;
+                               }
+
+                               if (lastIndex > 0 && lastIndex < block.Content.Length)
+                                       ret.Add (new TextBlock (TextBlockType.Verbatim, block.Content.Substring (lastIndex)));
+
+                               if (!foundMatches)
+                                       ret.Add (block);
+                       }
+
+                       return ret;
+               }
+               
+               IList SplitTextIntoBlocks (string text)
+               {
+                       var ret = new List <TextBlock> ();
+
+                       ret.Add (new TextBlock (TextBlockType.Verbatim, text));
+                       ret = FindRegexBlocks (clientCommentRegex, null, null, ret, TextBlockType.Comment, false);
+                       ret = FindRegexBlocks (runatServer, endOfTag, CheckTagEndNeeded, ret, TextBlockType.Tag, false);
+                       ret = FindRegexBlocks (expressionRegex, null, null, ret, TextBlockType.Expression, false);
+
+                       return ret;
+               }
+
                void TextParsed (ILocation location, string text)
                {
                        if (ignore_text)
                                return;
 
-                       if (text.IndexOf ("<%") != -1 && !inScript) {
-                               if (this.text.Length > 0)
-                                       FlushText ();
-                               CodeRenderParser r = new CodeRenderParser (text, stack.Builder);
-                               r.AddChildren ();
+                       if (inScript) {
+                               this.text.Append (text);
+                               FlushText (true);
                                return;
                        }
 
-                       this.text.Append (text);
-                       //PrintLocation (location);
+                       IList blocks = SplitTextIntoBlocks (text);
+                       foreach (TextBlock block in blocks) {
+                               switch (block.Type) {
+                                       case TextBlockType.Verbatim:
+                                               this.text.Append (block.Content);
+                                               break;
+
+                                       case TextBlockType.Expression:
+                                               if (this.text.Length > 0)
+                                                       FlushText (true);
+                                               CodeRenderParser r = new CodeRenderParser (block.Content, stack.Builder, location);
+                                               r.AddChildren (this);
+                                               break;
+
+                                       case TextBlockType.Tag:
+                                               ParseAttributeTag (block.Content, location);
+                                               break;
+
+                                       case TextBlockType.Comment: {
+                                               this.text.Append ("<!--");
+                                               FlushText (true);
+                                               string blockToParse = block.Content.Substring (4, block.Length - 7);
+                                               bool condEndif;
+                                               if (blockToParse.EndsWith ("<![endif]")) {
+                                                       blockToParse = blockToParse.Substring (0, blockToParse.Length - 9);
+                                                       condEndif = true;
+                                               } else
+                                                       condEndif = false;
+
+                                               AspParser outerParser = location as AspParser;
+                                               int positionOffset = outerParser != null ? outerParser.BeginPosition : 0;
+                                               AspParser parser = new AspParser ("@@comment_code@@", new StringReader (blockToParse), location.BeginLine - 1, positionOffset, outerParser);
+                                               parser.Error += new ParseErrorHandler (ParseError);
+                                               parser.TagParsed += new TagParsedHandler (TagParsed);
+                                               parser.TextParsed += new TextParsedHandler (TextParsed);
+                                               parser.Parse ();
+                                               if (condEndif)
+                                                       this.text.Append ("<![endif]");
+                                               this.text.Append ("-->");
+                                               FlushText (true);
+                                               break;
+                                       }
+                               }
+                       }
                }
 
                void FlushText ()
+               {
+                       FlushText (false);
+               }
+               
+               void FlushText (bool ignoreEmptyString)
                {
                        string t = text.ToString ();
                        text.Length = 0;
+
+                       if (ignoreEmptyString && t.Trim ().Length == 0)
+                               return;
+                       
                        if (inScript) {
-                               // TODO: store location
-                               tparser.Scripts.Add (t);
+                               PageParserFilter pfilter = PageParserFilter;
+                               if (pfilter != null && !pfilter.ProcessCodeConstruct (CodeConstructType.ScriptTag, t))
+                                       return;
+
+                               tparser.Scripts.Add (new ServerSideScript (t, new System.Web.Compilation.Location (tparser.Location)));
                                return;
                        }
 
@@ -474,15 +1165,81 @@ namespace System.Web.Compilation
                        }
                }
 
-               bool ProcessTag (string tagid, TagAttributes atts, TagType tagtype)
+               bool BuilderHasOtherThan (Type type, ControlBuilder cb)
+               {
+                       ArrayList al = cb.OtherTags;
+                       if (al != null && al.Count > 0)
+                               return true;
+                       
+                       al = cb.Children;
+                       if (al != null) {
+                               ControlBuilder tmp;
+                               
+                               foreach (object o in al) {
+                                       if (o == null)
+                                               continue;
+                                       
+                                       tmp = o as ControlBuilder;
+                                       if (tmp == null) {
+                                               string s = o as string;
+                                               if (s != null && String.IsNullOrEmpty (s.Trim ()))
+                                                       continue;
+                                               
+                                               return true;
+                                       }
+                                       
+                                       if (tmp is System.Web.UI.WebControls.ContentBuilderInternal)
+                                               continue;
+                                       
+                                       if (tmp.ControlType != typeof (System.Web.UI.WebControls.Content))
+                                               return true;
+                               }
+                       }
+
+                       return false;
+               }
+               
+               bool OtherControlsAllowed (ControlBuilder cb)
+               {
+                       if (cb == null)
+                               return true;
+                       
+                       if (!typeof (System.Web.UI.WebControls.Content).IsAssignableFrom (cb.ControlType))
+                               return true;
+
+                       if (BuilderHasOtherThan (typeof (System.Web.UI.WebControls.Content), RootBuilder))
+                               return false;
+                       
+                       return true;
+               }
+
+               public void AddControl (Type type, IDictionary attributes)
+               {
+                       ControlBuilder parent = stack.Builder;
+                       ControlBuilder builder = ControlBuilder.CreateBuilderFromType (tparser, parent, type, null, null,
+                                                                                      attributes, location.BeginLine,
+                                                                                      location.Filename);
+                       if (builder != null)
+                               parent.AppendSubBuilder (builder);
+               }
+               
+               bool ProcessTag (ILocation location, string tagid, TagAttributes atts, TagType tagtype, out bool ignored)
                {
+                       ignored = false;
                        if (isApplication) {
-                               if (String.Compare (tagid, "object", true, CultureInfo.InvariantCulture) != 0)
+                               if (String.Compare (tagid, "object", true, Helpers.InvariantCulture) != 0)
                                        throw new ParseException (location, "Invalid tag for application file.");
                        }
 
                        ControlBuilder parent = stack.Builder;
                        ControlBuilder builder = null;
+                       if (parent != null && parent.ControlType == typeof (HtmlTable) &&
+                           (String.Compare (tagid, "thead", true, Helpers.InvariantCulture) == 0 ||
+                            String.Compare (tagid, "tbody", true, Helpers.InvariantCulture) == 0)) {
+                               ignored = true;
+                               return true;
+                       }
+                               
                        Hashtable htable = (atts != null) ? atts.GetDictionary (null) : emptyHash;
                        if (stack.Count > 1) {
                                try {
@@ -494,15 +1251,22 @@ namespace System.Web.Compilation
                                }
                        }
 
-                       if (builder == null && atts != null && atts.IsRunAtServer ()) {
+                       bool runatServer = atts != null && atts.IsRunAtServer ();
+                       if (builder == null && runatServer) {
                                string id = htable ["id"] as string;
                                if (id != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (id))
                                        throw new ParseException (Location, "'" + id + "' is not a valid identifier");
                                        
                                try {
-                                       builder = rootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location);
+                                       builder = RootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location);
                                } catch (TypeLoadException e) {
                                        throw new ParseException (Location, "Type not found.", e);
+                               } catch (HttpException e) {
+                                       CompilationException inner = e.InnerException as CompilationException;
+                                       if (inner != null)
+                                               throw inner;
+                                       
+                                       throw new ParseException (Location, e.Message, e);
                                } catch (Exception e) {
                                        throw new ParseException (Location, e.Message, e);
                                }
@@ -511,7 +1275,20 @@ namespace System.Web.Compilation
                        if (builder == null)
                                return false;
 
-                       builder.location = location;
+                       // This is as good as we can do for now - if the parsed location contains
+                       // both expressions and code render blocks then we're out of luck...
+                       string plainText = location.PlainText;
+                       if (!runatServer && plainText.IndexOf ("<%$") == -1&& plainText.IndexOf ("<%") > -1)
+                               return false;
+
+                       PageParserFilter pfilter = PageParserFilter;
+                       if (pfilter != null && !pfilter.AllowControl (builder.ControlType, builder))
+                               throw new ParseException (Location, "Control type '" + builder.ControlType + "' not allowed.");
+                       
+                       if (!OtherControlsAllowed (builder))
+                               throw new ParseException (Location, "Only Content controls are allowed directly in a content page that contains Content controls.");
+                       
+                       builder.Location = location;
                        builder.ID = htable ["id"] as string;
                        if (typeof (HtmlForm).IsAssignableFrom (builder.ControlType)) {
                                if (inForm)
@@ -528,7 +1305,7 @@ namespace System.Web.Compilation
                        } else {
                                if (!isApplication && builder is ObjectTagBuilder) {
                                        ObjectTagBuilder ot = (ObjectTagBuilder) builder;
-                                       if (ot.Scope != null && ot.Scope != "")
+                                       if (ot.Scope != null && ot.Scope.Length > 0)
                                                throw new ParseException (location, "Scope not allowed here");
 
                                        if (tagtype == TagType.Tag) {
@@ -557,10 +1334,13 @@ namespace System.Web.Compilation
                {
                        if (tagtype != TagType.Close) {
                                if (attributes != null && attributes.IsRunAtServer ()) {
-                                       CheckLanguage ((string) attributes ["language"]);
+                                       string language = (string) attributes ["language"];
+                                       if (language != null && language.Length > 0 && tparser.ImplicitLanguage)
+                                               tparser.SetLanguage (language);
+                                       CheckLanguage (language);
                                        string src = (string) attributes ["src"];
                                        if (src != null) {
-                                               if (src == "")
+                                               if (src.Length == 0)
                                                        throw new ParseException (Parser,
                                                                "src cannot be an empty string");
 
@@ -584,7 +1364,14 @@ namespace System.Web.Compilation
                                                Parser.VerbatimID = "script";
                                                javascript = true;
                                        }
-                                       TextParsed (location, location.PlainText);
+                                       string content = location.PlainText;
+                                       /* HACK, HACK, HACK */
+                                       if (content.StartsWith ("<script")) {
+                                               TextParsed (location, "<script");
+                                               content = content.Substring (7);
+                                       }
+
+                                       TextParsed (location, content);
                                        return true;
                                }
                        }
@@ -608,9 +1395,9 @@ namespace System.Web.Compilation
                bool CloseControl (string tagid)
                {
                        ControlBuilder current = stack.Builder;
-                       string btag = current.TagName;
-                       if (String.Compare (btag, "tbody", true, CultureInfo.InvariantCulture) != 0 &&
-                           String.Compare (tagid, "tbody", true, CultureInfo.InvariantCulture) == 0) {
+                       string btag = current.OriginalTagName;
+                       if (String.Compare (btag, "tbody", true, Helpers.InvariantCulture) != 0 &&
+                           String.Compare (tagid, "tbody", true, Helpers.InvariantCulture) == 0) {
                                if (!current.ChildrenAsProperties) {
                                        try {
                                                TextParsed (location, location.PlainText);
@@ -619,8 +1406,11 @@ namespace System.Web.Compilation
                                }
                                return true;
                        }
+
+                       if (current.ControlType == typeof (HtmlTable) && String.Compare (tagid, "thead", true, Helpers.InvariantCulture) == 0)
+                               return true;
                        
-                       if (0 != String.Compare (tagid, btag, true, CultureInfo.InvariantCulture))
+                       if (0 != String.Compare (tagid, btag, true, Helpers.InvariantCulture))
                                return false;
 
                        // if (current is TemplateBuilder)
@@ -629,7 +1419,7 @@ namespace System.Web.Compilation
                                try { 
                                        current.SetTagInnerText (tagInnerText.ToString ());
                                } catch (Exception e) {
-                                       throw new ParseException (current.location, e.Message, e);
+                                       throw new ParseException (current.Location, e.Message, e);
                                }
 
                                tagInnerText.Length = 0;
@@ -645,8 +1435,38 @@ namespace System.Web.Compilation
                        return true;
                }
 
+               CodeConstructType MapTagTypeToConstructType (TagType tagtype)
+               {
+                       switch (tagtype) {
+                               case TagType.CodeRenderExpression:
+                                       return CodeConstructType.ExpressionSnippet;
+
+                               case TagType.CodeRender:
+                                       return CodeConstructType.CodeSnippet;
+
+                               case TagType.DataBinding:
+                                       return CodeConstructType.DataBindingSnippet;
+
+                               default:
+                                       throw new InvalidOperationException ("Unexpected tag type.");
+                       }
+               }
+               
                bool ProcessCode (TagType tagtype, string code, ILocation location)
                {
+                       PageParserFilter pfilter = PageParserFilter;
+                       // LAMESPEC:
+                       //
+                       // http://msdn.microsoft.com/en-us/library/system.web.ui.pageparserfilter.processcodeconstruct.aspx
+                       //
+                       // The above page says if false is returned then we should NOT process the
+                       // code further, wheras in reality it's the other way around. The
+                       // ProcessCodeConstruct return value means whether or not the filter
+                       // _processed_ the code.
+                       //
+                       if (pfilter != null && (!pfilter.AllowCode || pfilter.ProcessCodeConstruct (MapTagTypeToConstructType (tagtype), code)))
+                               return true;
+
                        ControlBuilder b = null;
                        if (tagtype == TagType.CodeRender)
                                b = new CodeRenderBuilder (code, false, location);
@@ -670,16 +1490,11 @@ namespace System.Web.Compilation
                        if (lang == null || lang == "")
                                return;
 
-                       if (String.Compare (lang, tparser.Language, true, CultureInfo.InvariantCulture) == 0)
+                       if (String.Compare (lang, tparser.Language, true, Helpers.InvariantCulture) == 0)
                                return;
 
-#if NET_2_0
-                       CompilationSection section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
+                       CompilationSection section = (CompilationSection) WebConfigurationManager.GetWebApplicationSection ("system.web/compilation");
                        if (section.Compilers[tparser.Language] != section.Compilers[lang])
-#else
-                       CompilationConfiguration cfg = CompilationConfiguration.GetInstance (HttpContext.Current); 
-                       if (!cfg.Compilers.CompareLanguages (tparser.Language, lang))
-#endif
                                throw new ParseException (Location,
                                                String.Format ("Trying to mix language '{0}' and '{1}'.", 
                                                                tparser.Language, lang));
@@ -690,22 +1505,63 @@ namespace System.Web.Compilation
                {
                        string str;
                        ControlBuilder builder;
-
-                       public CodeRenderParser (string str, ControlBuilder builder)
+                       AspGenerator generator;
+                       ILocation location;
+                       
+                       public CodeRenderParser (string str, ControlBuilder builder, ILocation location)
                        {
                                this.str = str;
                                this.builder = builder;
+                               this.location = location;
                        }
 
-                       public void AddChildren ()
+                       public void AddChildren (AspGenerator generator)
                        {
+                               this.generator = generator;
                                int index = str.IndexOf ("<%");
-                               if (index > 0) {
-                                       TextParsed (null, str.Substring (0, index));
-                                       str = str.Substring (index);
-                               }
+                               if (index > 0)
+                                       DoParseExpressions (str);
+                               else
+                                       DoParse (str);
+                       }
 
-                               AspParser parser = new AspParser ("@@inner_string@@", new StringReader (str));
+                       void DoParseExpressions (string str)
+                       {
+                               int startIndex = 0, index = 0;
+                               Regex codeDirective = new Regex ("(<%(?!@)(?<code>(.|\\s)*?)%>)|(<[\\w:\\.]+.*?runat=[\"']?server[\"']?.*?/>)",
+                                                                RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.CultureInvariant);
+                               Match match;
+                               int strLen = str.Length;
+                               
+                               while (index > -1 && startIndex < strLen) {
+                                       match = codeDirective.Match (str, index);
+                                       
+                                       if (match.Success) {
+                                               string value = match.Value;
+                                               index = match.Index;
+                                               if (index > startIndex)
+                                                       TextParsed (null, str.Substring (startIndex, index - startIndex));
+                                               DoParse (value);
+                                               index += value.Length;
+                                               startIndex = index;
+                                       } else
+                                               break;
+
+                                       if (index < strLen)
+                                               index = str.IndexOf ('<', index);
+                                       else
+                                               break;
+                               }
+                               
+                               if (startIndex < strLen)
+                                       TextParsed (null, str.Substring (startIndex));
+                       }
+                       
+                       void DoParse (string str)
+                       {
+                               AspParser outerParser = location as AspParser;
+                               int positionOffset = outerParser != null ? outerParser.BeginPosition : 0;
+                               AspParser parser = new AspParser ("@@code_render@@", new StringReader (str), location.BeginLine - 1, positionOffset, outerParser);
                                parser.Error += new ParseErrorHandler (ParseError);
                                parser.TagParsed += new TagParsedHandler (TagParsed);
                                parser.TextParsed += new TextParsedHandler (TextParsed);
@@ -714,14 +1570,34 @@ namespace System.Web.Compilation
 
                        void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
                        {
-                               if (tagtype == TagType.CodeRender)
-                                       builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location));
-                               else if (tagtype == TagType.CodeRenderExpression)
-                                       builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location));
-                               else if (tagtype == TagType.DataBinding)
-                                       builder.AppendSubBuilder (new DataBindingBuilder (tagid, location));
-                               else
-                                       builder.AppendLiteralString (location.PlainText);
+                               switch (tagtype) {
+                                       case TagType.CodeRender:
+                                               builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location));
+                                               break;
+                                               
+                                       case TagType.CodeRenderExpression:
+                                               builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location));
+                                               break;
+                                               
+                                       case TagType.DataBinding:
+                                               builder.AppendSubBuilder (new DataBindingBuilder (tagid, location));
+                                               break;
+
+                                       case TagType.Tag:
+                                       case TagType.SelfClosing:
+                                       case TagType.Close:
+                                               if (generator != null)
+                                                       generator.TagParsed (location, tagtype, tagid, attributes);
+                                               else
+                                                       goto default;
+                                               break;
+                                               
+                                       default:
+                                               string text = location.PlainText;
+                                               if (text != null && text.Trim ().Length > 0)
+                                                       builder.AppendLiteralString (text);
+                                               break;
+                               }
                        }
 
                        void TextParsed (ILocation location, string text)