Ver código fonte

2009-05-04 Marek Habersack <[email protected]>

	* ThemeDirectoryBuildProvider.cs: use new AspGenerator constructor
	which takes AspComponentFoundery as its
	parameter. AspGenerator.RootBuilder isn't initialized until after
	the parser is initialized.

	* TemplateControlCompiler.cs: make sure to call
	ProcessGeneratedCode on each instance of ControlBuilder after it's
	fully generated. Fixes bug #500075

	* TemplateBuildProvider.cs: moved the directive regex to
	AspGenerator

	* BaseCompiler.cs: added two internal properties, BaseType and
	DerivedType, which are used in calls to
	ControlBuilder.ProcessGeneratedCode. Fixes bug #500075

	* AspGenerator.cs: implemented a work around for our parser
	limitation which is unable to extract the page/control's base type
	before parsing the actual file contents. This is necessary to
	support ASP.NET MVC's "generic" Inherits attribute. The reason we
	need to find out what the base type is before parsing the file is
	that we need to look at the type's custom attributes to see if a
	root control builder type is specified (which is what ASP.NET MVC
	does) and, if yes, use it instead of the default type. Fixes bug
	#500075

2009-05-04  Marek Habersack  <[email protected]>

	* TemplateParser.cs: take RootBuilder from the associated
	generator.
	Allow the main page directive to be added twice if the parser
	needs it. Used by AspGenerator when extracting the inherited type
	name before actual parsing. Fixes bug #500075

	* FileLevelControlBuilderAttribute.cs,
	FileLevelPageControlBuilder.cs, FileLevelUserControlBuilder.cs:
	implemented

	* ControlBuilder.cs: added a new internal property,
	DataBindingMethod.
	Implemented ProcessGeneratedCode.

svn path=/trunk/mcs/; revision=133500
Marek Habersack 16 anos atrás
pai
commit
8aa3c10397

+ 207 - 6
mcs/class/System.Web/System.Web.Compilation/AspGenerator.cs

@@ -179,6 +179,11 @@ namespace System.Web.Compilation
 
 	class AspGenerator
 	{
+#if NET_2_0
+		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);
+#endif
 		ParserStack pstack;
 		BuilderLocationStack stack;
 		TemplateParser tparser;
@@ -192,20 +197,32 @@ namespace System.Web.Compilation
 		bool inForm;
 		bool useOtherTags;
 		TagType lastTag;
+#if NET_2_0
+		AspComponentFoundry componentFoundry;
+		Stream inputStream;
 
+		public AspGenerator (TemplateParser tparser, AspComponentFoundry componentFoundry) : this (tparser)
+		{
+			this.componentFoundry = componentFoundry;
+		}
+#endif
+		
 		public AspGenerator (TemplateParser tparser)
 		{
 			this.tparser = tparser;
 			text = new StringBuilder ();
 			stack = new BuilderLocationStack ();
+
+#if !NET_2_0
 			rootBuilder = new RootBuilder (tparser);
-			stack.Push (rootBuilder, null);
 			tparser.RootBuilder = rootBuilder;
+			stack.Push (rootBuilder, null);
+#endif
 			pstack = new ParserStack ();
 		}
 
 		public RootBuilder RootBuilder {
-			get { return tparser.RootBuilder; }
+			get { return rootBuilder; }
 		}
 
 		public AspParser Parser {
@@ -225,6 +242,186 @@ namespace System.Web.Compilation
 				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;
+		}
+		
+		Type GetInheritedType (string fileContents)
+		{
+			MatchCollection matches = DirectiveRegex.Matches (fileContents);
+			if (matches == null || matches.Count == 0)
+				return null;
+
+			string wantedDirectiveName = tparser.DefaultDirectiveName.ToLower ();
+			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 (), wantedDirectiveName, StringComparison.Ordinal) != 0)
+					continue;
+
+				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);
+				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)
+		{
+			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;
+		}
 #endif
 		
 		BaseCompiler GetCompilerFromType ()
@@ -255,6 +452,7 @@ namespace System.Web.Compilation
 #if NET_2_0
 			parser.ParsingComplete += new ParsingCompleteHandler (ParsingCompleted);
 			tparser.AspGenerator = this;
+			CreateRootBuilder (inputStream, filename);
 #endif
 			if (!pstack.Push (parser))
 				throw new ParseException (Location, "Infinite recursion detected including file: " + filename);
@@ -303,7 +501,7 @@ namespace System.Web.Compilation
 				pstack.Pop ();
 
 #if DEBUG
-				PrintTree (rootBuilder, 0);
+				PrintTree (RootBuilder, 0);
 #endif
 
 				if (stack.Count > 1 && pstack.Count == 0)
@@ -318,6 +516,9 @@ namespace System.Web.Compilation
 
 		public void Parse (Stream stream, string filename, bool doInitParser)
 		{
+#if NET_2_0
+			inputStream = stream;
+#endif
 			Parse (new StreamReader (stream, WebEncoding.FileEncoding), filename, doInitParser);
 		}
 		
@@ -523,7 +724,7 @@ namespace System.Web.Compilation
 			if (pfilter == null)
 				return;
 
-			pfilter.ParseComplete (rootBuilder);
+			pfilter.ParseComplete (RootBuilder);
 		}
 #endif
 		
@@ -761,7 +962,7 @@ namespace System.Web.Compilation
 			if (!typeof (System.Web.UI.WebControls.Content).IsAssignableFrom (cb.ControlType))
 				return true;
 
-			if (BuilderHasOtherThan (typeof (System.Web.UI.WebControls.Content), rootBuilder))
+			if (BuilderHasOtherThan (typeof (System.Web.UI.WebControls.Content), RootBuilder))
 				return false;
 			
 			return true;
@@ -813,7 +1014,7 @@ namespace System.Web.Compilation
 					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 (Exception e) {

+ 14 - 0
mcs/class/System.Web/System.Web.Compilation/BaseCompiler.cs

@@ -836,6 +836,20 @@ namespace System.Web.Compilation
 			get { return unit; }
 		}
 
+#if NET_2_0
+		internal CodeTypeDeclaration DerivedType {
+			get { return mainClass; }
+		}
+
+		internal CodeTypeDeclaration BaseType {
+			get {
+				if (partialClass == null)
+					return DerivedType;
+				return partialClass;
+			}
+		}
+#endif
+
 		internal TemplateParser Parser {
 			get { return parser; }
 		}

+ 28 - 0
mcs/class/System.Web/System.Web.Compilation/ChangeLog

@@ -1,3 +1,31 @@
+2009-05-04  Marek Habersack  <[email protected]>
+
+	* ThemeDirectoryBuildProvider.cs: use new AspGenerator constructor
+	which takes AspComponentFoundery as its
+	parameter. AspGenerator.RootBuilder isn't initialized until after
+	the parser is initialized.
+
+	* TemplateControlCompiler.cs: make sure to call
+	ProcessGeneratedCode on each instance of ControlBuilder after it's
+	fully generated. Fixes bug #500075
+
+	* TemplateBuildProvider.cs: moved the directive regex to
+	AspGenerator
+
+	* BaseCompiler.cs: added two internal properties, BaseType and
+	DerivedType, which are used in calls to
+	ControlBuilder.ProcessGeneratedCode. Fixes bug #500075
+
+	* AspGenerator.cs: implemented a work around for our parser
+	limitation which is unable to extract the page/control's base type
+	before parsing the actual file contents. This is necessary to
+	support ASP.NET MVC's "generic" Inherits attribute. The reason we
+	need to find out what the base type is before parsing the file is
+	that we need to look at the type's custom attributes to see if a
+	root control builder type is specified (which is what ASP.NET MVC
+	does) and, if yes, use it instead of the default type. Fixes bug
+	#500075
+
 2009-04-30  Marek Habersack  <[email protected]>
 
 	* BuildManager.cs: when BuildInner catches a compilation

+ 1 - 2
mcs/class/System.Web/System.Web.Compilation/TemplateBuildProvider.cs

@@ -47,7 +47,6 @@ namespace System.Web.Compilation
 	{
 		delegate void ExtractDirectiveDependencies (string baseDirectory, CaptureCollection names, CaptureCollection values, TemplateBuildProvider bp);
 		
-		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 SortedDictionary <string, ExtractDirectiveDependencies> directiveAttributes;
 		static char[] directiveValueTrimChars = {' ', '\t', '\r', '\n', '"', '\''};
 
@@ -247,7 +246,7 @@ namespace System.Web.Compilation
 			if (String.IsNullOrEmpty (input))
 				return null;
 
-			MatchCollection matches = directiveRegex.Matches (input);
+			MatchCollection matches = AspGenerator.DirectiveRegex.Matches (input);
 			if (matches == null || matches.Count == 0)
 				return null;
 			

+ 24 - 26
mcs/class/System.Web/System.Web.Compilation/TemplateControlCompiler.cs

@@ -537,7 +537,6 @@ namespace System.Web.Compilation
 				valueExpression = new CodeSnippetExpression (value);
 			
 			method = CreateDBMethod (builder, dbMethodName, GetContainerType (builder), builder.ControlType);
-			
 			CodeVariableReferenceExpression targetExpr = new CodeVariableReferenceExpression ("target");
 
 			// This should be a CodePropertyReferenceExpression for properties... but it works anyway
@@ -1096,6 +1095,9 @@ namespace System.Web.Compilation
 			AddEventAssign (method, builder, "DataBinding", typeof (EventHandler), dbMethodName);
 
 			method = CreateDBMethod (builder, dbMethodName, GetContainerType (builder), builder.ControlType);
+#if NET_2_0
+			builder.DataBindingMethod = method;
+#endif
 			CodeCastExpression cast;
 			CodeMethodReferenceExpression methodExpr;
 			CodeMethodInvokeExpression expr;
@@ -1413,7 +1415,9 @@ namespace System.Web.Compilation
 
 			// Add the DataBind handler
 			method = CreateDBMethod (builder, dbMethodName, GetContainerType (builder), typeof (DataBoundLiteralControl));
-
+#if NET_2_0
+			builder.DataBindingMethod = method;
+#endif
 			CodeVariableReferenceExpression targetExpr = new CodeVariableReferenceExpression ("target");
 			CodeMethodInvokeExpression invoke = new CodeMethodInvokeExpression ();
 			invoke.Method = new CodeMethodReferenceExpression (targetExpr, "SetDataBoundString");
@@ -1428,7 +1432,7 @@ namespace System.Web.Compilation
 			method.Statements.Add (AddLinePragma (invoke, builder));
 			
 			mainClass.Members.Add (method);
-
+			
 			AddChildCall (builder, db);
 		}
 
@@ -1491,11 +1495,8 @@ namespace System.Web.Compilation
 					FlushText (builder, sb);
 					if (b is ObjectTagBuilder) {
 						ProcessObjectTag ((ObjectTagBuilder) b);
-						continue;
-					}
-
-					StringPropertyBuilder pb = b as StringPropertyBuilder;
-					if (pb != null){
+					} else if (b is StringPropertyBuilder) {
+						StringPropertyBuilder pb = b as StringPropertyBuilder;
 						if (pb.Children != null && pb.Children.Count > 0) {
 							StringBuilder asb = new StringBuilder ();
 							foreach (string s in pb.Children)
@@ -1506,11 +1507,9 @@ namespace System.Web.Compilation
 							assign.Right = new CodePrimitiveExpression (asb.ToString ());
 							method.Statements.Add (AddLinePragma (assign, builder));
 						}
-						continue;
 					}
-
 #if NET_2_0
-					if (b is ContentBuilderInternal) {
+					else if (b is ContentBuilderInternal) {
 						ContentBuilderInternal cb = (ContentBuilderInternal) b;
 						CreateControlTree (cb, false, true);
 						AddContentTemplateInvocation (cb, builder.Method, cb.Method.Name);
@@ -1518,33 +1517,28 @@ namespace System.Web.Compilation
 					}
 #endif
 					// Ignore TemplateBuilders - they are processed in InitMethod
-					if (b is TemplateBuilder)
-						continue;
-
-					if (b is CodeRenderBuilder) {
+					else if (b is TemplateBuilder) {
+					} else if (b is CodeRenderBuilder) {
 						AddCodeRender (builder, (CodeRenderBuilder) b);
-						continue;
-					}
-
-					if (b is DataBindingBuilder) {
+					} else if (b is DataBindingBuilder) {
 						AddDataBindingLiteral (builder, (DataBindingBuilder) b);
-						continue;
-					}
-					
-					if (b is ControlBuilder) {
+					} else if (b is ControlBuilder) {
 						ControlBuilder child = (ControlBuilder) b;
 						CreateControlTree (child, inTemplate, builder.ChildrenAsProperties);
 						AddChildCall (builder, child);
 						continue;
-					}
+					} else
+						throw new Exception ("???");
 
-					throw new Exception ("???");
+#if NET_2_0
+					ControlBuilder bldr = b as ControlBuilder;
+					bldr.ProcessGeneratedCode (CompileUnit, BaseType, DerivedType, bldr.Method, bldr.DataBindingMethod);
+#endif
 				}
 
 				FlushText (builder, sb);
 			}
 
-
 			ControlBuilder defaultPropertyBuilder = builder.DefaultPropertyBuilder;
 			if (defaultPropertyBuilder != null) {
 				CreateControlTree (defaultPropertyBuilder, false, true);
@@ -1580,6 +1574,10 @@ namespace System.Web.Compilation
 
 			if (!childrenAsProperties && typeof (Control).IsAssignableFrom (builder.ControlType))
 				builder.Method.Statements.Add (new CodeMethodReturnStatement (ctrlVar));
+
+#if NET_2_0
+			builder.ProcessGeneratedCode (CompileUnit, BaseType, DerivedType, builder.Method, builder.DataBindingMethod);
+#endif
 		}
 
 #if NET_2_0

+ 1 - 2
mcs/class/System.Web/System.Web.Compilation/ThemeDirectoryBuildProvider.cs

@@ -94,8 +94,7 @@ namespace System.Web.Compilation
 				PageThemeFileParser ptfp = new PageThemeFileParser (new VirtualPath (skin_file_url), skin_file, context);
 
 				ptp.AddDependency (skin_file_url);
-				generator = new AspGenerator (ptfp);
-				ptfp.RootBuilder.Foundry = shared_foundry;
+				generator = new AspGenerator (ptfp, shared_foundry);
 				generator.Parse ();
 
 				if (ptfp.RootBuilder.Children != null)

+ 16 - 0
mcs/class/System.Web/System.Web.UI/ChangeLog

@@ -1,3 +1,19 @@
+2009-05-04  Marek Habersack  <[email protected]>
+
+	* TemplateParser.cs: take RootBuilder from the associated
+	generator. 
+	Allow the main page directive to be added twice if the parser
+	needs it. Used by AspGenerator when extracting the inherited type
+	name before actual parsing. Fixes bug #500075
+
+	* FileLevelControlBuilderAttribute.cs,
+	FileLevelPageControlBuilder.cs, FileLevelUserControlBuilder.cs:
+	implemented
+
+	* ControlBuilder.cs: added a new internal property,
+	DataBindingMethod.
+	Implemented ProcessGeneratedCode.
+
 2009-04-30  Marek Habersack  <[email protected]>
 
 	* TemplateParser.cs: removed the PageParserFilterTypeName

+ 13 - 7
mcs/class/System.Web/System.Web.UI/ControlBuilder.cs

@@ -142,6 +142,13 @@ namespace System.Web.UI {
 			set { method = value; }
 		}
 
+#if NET_2_0
+		internal CodeMemberMethod DataBindingMethod {
+			get;
+			set;
+		}
+#endif
+			
 		internal CodeStatementCollection MethodStatements {
 			get { return methodStatements; }
 			set { methodStatements = value; }
@@ -317,7 +324,7 @@ namespace System.Web.UI {
 		
 		internal RootBuilder Root {
 			get {
-				if (GetType () == typeof (RootBuilder))
+				if (typeof (RootBuilder).IsAssignableFrom (GetType ()))
 					return (RootBuilder) this;
 
 				return (RootBuilder) parentBuilder.Root;
@@ -754,14 +761,13 @@ namespace System.Web.UI {
 			return CreateInstance ();
 		}
 		
-		[MonoTODO]
 		public virtual void ProcessGeneratedCode(CodeCompileUnit codeCompileUnit,
-			CodeTypeDeclaration baseType,
-			CodeTypeDeclaration derivedType,
-			CodeMemberMethod buildMethod,
-			CodeMemberMethod dataBindingMethod)
+							 CodeTypeDeclaration baseType,
+							 CodeTypeDeclaration derivedType,
+							 CodeMemberMethod buildMethod,
+							 CodeMemberMethod dataBindingMethod)
 		{
-			throw new NotImplementedException ();
+			// nothing to do
 		}
 
 		internal void ResetState()

+ 31 - 8
mcs/class/System.Web/System.Web.UI/FileLevelControlBuilderAttribute.cs

@@ -3,8 +3,10 @@
 //
 // Authors:
 //     Arina Itkes ([email protected])
+//     Marek Habersack ([email protected])
 //
 // (C) 2007 Mainsoft Co. (http://www.mainsoft.com)
+// (C) 2009 Novell, Inc (http://novell.com/)
 //
 //
 //
@@ -34,28 +36,49 @@ namespace System.Web.UI
 	[AttributeUsageAttribute (AttributeTargets.Class)]
 	public sealed class FileLevelControlBuilderAttribute : Attribute
 	{
+		public static readonly FileLevelControlBuilderAttribute Default = new FileLevelControlBuilderAttribute (null);
+		
 		public FileLevelControlBuilderAttribute (Type builderType)
-		{}
-
-		public static readonly FileLevelControlBuilderAttribute Default;
+		{
+			this.BuilderType = builderType;
+		}
 		
-		public Type BuilderType { get { throw new NotImplementedException (); } }
+		public Type BuilderType {
+			get;
+			private set;
+		}
 		
 		public override bool Equals (Object obj)
 		{
-			throw new NotImplementedException ();
+			var attr = obj as FileLevelControlBuilderAttribute;
+			return ((attr != null) && this.BuilderType == attr.BuilderType);
 		}
+		
 		public new static bool Equals (Object objA, Object objB)
 		{
-			throw new NotImplementedException ();
+			var attrA = objA as FileLevelControlBuilderAttribute;
+			if (attrA == null)
+				return false;
+
+			var attrB = objB as FileLevelControlBuilderAttribute;
+			if (attrB == null)
+				return false;
+
+			return (attrA.BuilderType == attrB.BuilderType);
 		}
+			
 		public override int GetHashCode ()
 		{
-			throw new NotImplementedException ();
+			Type type = BuilderType;
+			if (type == null)
+				return base.GetHashCode ();
+
+			return type.GetHashCode ();
 		}
+		
 		public override bool IsDefaultAttribute ()
 		{
-			throw new NotImplementedException ();
+			return Equals (Default);
 		}
 	}
 }

+ 34 - 4
mcs/class/System.Web/System.Web.UI/FileLevelPageControlBuilder.cs

@@ -1,10 +1,12 @@
 //
-// System.Web.UI.FileLevelControlBuilderAttribute.cs
+// System.Web.UI.FileLevelControlBuilder.cs
 //
 // Authors:
 //     Arina Itkes ([email protected])
+//     Marek Habersack <[email protected]>
 //
 // (C) 2007 Mainsoft Co. (http://www.mainsoft.com)
+// (C) 2009 Novell, Inc (http://novell.com/)
 //
 //
 //
@@ -30,22 +32,50 @@
 
 #if NET_2_0
 using System.Web.UI;
+using System.Web.UI.WebControls;
 
 namespace System.Web.UI
 {
 	public class FileLevelPageControlBuilder : RootBuilder
 	{
+		bool hasContentControls;
+		bool hasLiteralControls;
+		bool hasOtherControls;
+		
 		public FileLevelPageControlBuilder ()
 		{
-			throw new NotImplementedException ();
 		}
+		
 		public override void AppendLiteralString (string text)
 		{
-			throw new NotImplementedException ();
+			bool emptyText = text != null ? text.Trim ().Length == 0 : true;
+			if (hasContentControls && !emptyText)
+				throw new HttpException ("Literal strings cannot be appended to Content pages.");
+
+			if (!emptyText)
+				hasLiteralControls = true;
+			
+			base.AppendLiteralString (text);
 		}
+		
 		public override void AppendSubBuilder (ControlBuilder subBuilder)
 		{
-			throw new NotImplementedException ();
+			if (subBuilder == null) {
+				base.AppendSubBuilder (subBuilder);
+				return;
+			}
+			
+			if (typeof (ContentBuilderInternal).IsAssignableFrom (subBuilder.GetType ())) {
+				if (hasOtherControls)
+					throw new HttpException ("Only Content controls are supported on content pages.");
+				
+				hasContentControls = true;
+				if (hasLiteralControls)
+					throw new HttpParseException ("Only Content controls are supported on content pages.");
+			} else
+				hasOtherControls = true;
+			
+			base.AppendSubBuilder (subBuilder);
 		}
 	}
 }

+ 0 - 1
mcs/class/System.Web/System.Web.UI/FileLevelUserControlBuilder.cs

@@ -37,7 +37,6 @@ namespace System.Web.UI
 	{
 		public FileLevelUserControlBuilder ()
 		{
-			throw new NotImplementedException ();
 		}
 	}
 }

+ 2 - 0
mcs/class/System.Web/System.Web.UI/RootBuilder.cs

@@ -43,6 +43,8 @@ namespace System.Web.UI {
 
 		public RootBuilder ()
 		{
+			foundry = new AspComponentFoundry ();
+			Line = 1;
 		}
 #else
 	public sealed class RootBuilder : TemplateBuilder {

+ 2 - 0
mcs/class/System.Web/System.Web.UI/TemplateBuilder.cs

@@ -109,6 +109,8 @@ namespace System.Web.UI {
 					  IDictionary attribs)
 		{
 			// enough?
+			if (parser != null)
+				FileName = parser.InputFile;
 			base.Init (parser, parentBuilder, type, tagName, ID, attribs);
 		}
 		

+ 35 - 7
mcs/class/System.Web/System.Web.UI/TemplateParser.cs

@@ -120,6 +120,10 @@ namespace System.Web.UI {
 		OutputCacheLocation oc_location;
 		CultureInfo invariantCulture = CultureInfo.InvariantCulture;
 #if NET_2_0
+		// Kludge needed to support pre-parsing of the main directive (see
+		// AspNetGenerator.GetRootBuilderType)
+		internal int allowedMainDirectives = 0;
+		
 		byte[] md5checksum;
 		string src;
 		bool srcIsLegacy;
@@ -316,18 +320,33 @@ namespace System.Web.UI {
 		{
 #if NET_2_0
 			var pageParserFilter = PageParserFilter;
-			if (pageParserFilter != null)
-				pageParserFilter.PreprocessDirective (directive.ToLower (CultureInfo.InvariantCulture), atts);
 #endif
 			if (String.Compare (directive, DefaultDirectiveName, true) == 0) {
-				if (mainAttributes != null)
+#if NET_2_0
+				bool allowMainDirective = allowedMainDirectives > 0;
+#else
+				bool allowMainDirective = false;
+#endif
+				if (mainAttributes != null && !allowMainDirective)
 					ThrowParseException ("Only 1 " + DefaultDirectiveName + " is allowed");
-
+#if NET_2_0
+				allowedMainDirectives--;
+				if (mainAttributes != null)
+					return;
+				
+				if (pageParserFilter != null)
+					pageParserFilter.PreprocessDirective (directive.ToLower (CultureInfo.InvariantCulture), atts);
+#endif
+				
 				mainAttributes = atts;
 				ProcessMainAttributes (mainAttributes);
 				return;
 			}
-
+#if NET_2_0
+			else if (pageParserFilter != null)
+				pageParserFilter.PreprocessDirective (directive.ToLower (CultureInfo.InvariantCulture), atts);
+#endif
+				
 			int cmp = String.Compare ("Assembly", directive, true);
 			if (cmp == 0) {
 				string name = GetString (atts, "Name", null);
@@ -728,7 +747,7 @@ namespace System.Web.UI {
 			explicitOn = GetBool (atts, "Explicit", compConfig.Explicit);
 			if (atts.ContainsKey ("LinePragmas"))
 				linePragmasOn = GetBool (atts, "LinePragmas", true);
-			
+
 			string inherits = GetString (atts, "Inherits", null);
 #if NET_2_0
 			string srcRealPath = null;
@@ -1308,7 +1327,16 @@ namespace System.Web.UI {
 		}
 
 		internal RootBuilder RootBuilder {
-			get { return rootBuilder; }
+			get {
+#if NET_2_0
+				if (rootBuilder != null)
+					return rootBuilder;
+				AspGenerator generator = AspGenerator;
+				if (generator != null)
+					rootBuilder = generator.RootBuilder;
+#endif
+				return rootBuilder;
+			}
 			set { rootBuilder = value; }
 		}