// // System.Web.Compilation.AspGenerator // // Authors: // Gonzalo Paniagua Javier (gonzalo@ximian.com) // // (C) 2002,2003 Ximian, Inc (http://www.ximian.com) // using System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Diagnostics; using System.IO; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Web.Util; namespace System.Web.Compilation { class ControlStack { private Stack controls; private ControlStackData top; private bool space_between_tags; private bool sbt_valid; class ControlStackData { public Type controlType; public string controlID; public string tagID; public ChildrenKind childKind; public string defaultPropertyName; public int childrenNumber; public Type container; public StringBuilder dataBindFunction; public StringBuilder codeRenderFunction; public bool useCodeRender; public int codeRenderIndex; public ControlStackData (Type controlType, string controlID, string tagID, ChildrenKind childKind, string defaultPropertyName, Type container) { this.controlType = controlType; this.controlID = controlID; this.tagID = tagID; this.childKind = childKind; this.defaultPropertyName = defaultPropertyName; this.container = container; childrenNumber = 0; } public override string ToString () { return controlType + " " + controlID + " " + tagID + " " + childKind + " " + childrenNumber; } } public ControlStack () { controls = new Stack (); } private Type GetContainerType (Type type) { if (type != typeof (System.Web.UI.Control) && !type.IsSubclassOf (typeof (System.Web.UI.Control))) return null; Type container_type; if (type == typeof (System.Web.UI.WebControls.DataList)) container_type = typeof (System.Web.UI.WebControls.DataListItem); else if (type == typeof (System.Web.UI.WebControls.DataGrid)) container_type = typeof (System.Web.UI.WebControls.DataGridItem); else if (type == typeof (System.Web.UI.WebControls.Repeater)) container_type = typeof (System.Web.UI.WebControls.RepeaterItem); else if (type == typeof (ListControl) || type.IsSubclassOf (typeof (ListControl))) container_type = type; else container_type = Container; return container_type; } public void Push (object o) { if (!(o is ControlStackData)) return; controls.Push (o); top = (ControlStackData) o; sbt_valid = false; } public void Push (Type controlType, string controlID, string tagID, ChildrenKind childKind, string defaultPropertyName) { Type container_type = null; if (controlType != null){ AddChild (); container_type = GetContainerType (controlType); if (container_type == null) container_type = this.Container; } top = new ControlStackData (controlType, controlID, tagID, childKind, defaultPropertyName, container_type); sbt_valid = false; controls.Push (top); } public object Pop () { object item = controls.Pop (); if (controls.Count != 0) top = (ControlStackData) controls.Peek (); sbt_valid = false; return item; } public Type PeekType () { return top.controlType; } public string PeekControlID () { return top.controlID; } public string PeekTagID () { return top.tagID; } public ChildrenKind PeekChildKind () { return top.childKind; } public string PeekDefaultPropertyName () { return top.defaultPropertyName; } public void AddChild () { if (top != null) top.childrenNumber++; } public bool HasDataBindFunction () { if (top.dataBindFunction == null || top.dataBindFunction.Length == 0) return false; return true; } public bool UseCodeRender { get { if (top.codeRenderFunction == null || top.codeRenderFunction.Length == 0) return false; return top.useCodeRender; } set { top.useCodeRender= value; } } public bool SpaceBetweenTags { get { if (!sbt_valid){ sbt_valid = true; Type type = top.controlType; if (type.Namespace == "System.Web.UI.WebControls") space_between_tags = true; else if (type.IsSubclassOf (typeof (System.Web.UI.WebControls.WebControl))) space_between_tags = true; else if (type == typeof (System.Web.UI.HtmlControls.HtmlSelect)) space_between_tags = true; else if (type == typeof (System.Web.UI.HtmlControls.HtmlTable)) space_between_tags = true; else if (type == typeof (System.Web.UI.HtmlControls.HtmlTableRow)) space_between_tags = true; else if (type == typeof (System.Web.UI.HtmlControls.HtmlTableCell)) space_between_tags = true; else space_between_tags = false; } return space_between_tags; } } public Type Container { get { if (top == null) return null; return top.container; } } public StringBuilder DataBindFunction { get { if (top.dataBindFunction == null) top.dataBindFunction = new StringBuilder (); return top.dataBindFunction; } } public int CodeRenderIndex { get { return top.codeRenderIndex++; } } public StringBuilder CodeRenderFunction { get { if (top.codeRenderFunction == null) top.codeRenderFunction = new StringBuilder (); return top.codeRenderFunction; } } public int ChildIndex { get { return top.childrenNumber - 1; } } public int Count { get { return controls.Count; } } public override string ToString () { return top.ToString () + " " + top.useCodeRender; } } enum ScriptStatus { Close, Open, Text } class AspGenerator { object [] parts; StringBuilder prolog; StringBuilder declarations; StringBuilder script; StringBuilder constructor; StringBuilder init_funcs; StringBuilder epilog; StringBuilder current_function; StringBuilder textChunk; Stack functions; ControlStack controls; bool parse_ok; bool has_form_tag; AspComponentFoundry aspFoundry; string classDecl; string className; string interfaces; string basetype; string parent; Type parentType; string fullPath; Hashtable options; string privateBinPath; string main_directive; static string app_file_wrong = "The content in the application file is not valid."; bool isPage; bool isUserControl; bool isApplication; Element current; ScriptStatus sstatus = ScriptStatus.Close; string waitClosing; ArrayList dependencies; HttpContext context; SessionState sessionState = SessionState.Enabled; static Type styleType = typeof (System.Web.UI.WebControls.Style); static Type fontinfoType = typeof (System.Web.UI.WebControls.FontInfo); enum UserControlResult { OK = 0, FileNotFound = 1, CompilationFailed = 2 } enum SessionState { Enabled, ReadOnly, Disabled } public AspGenerator (string pathToFile) { if (pathToFile == null) throw new ArgumentNullException ("pathToFile"); string filename = Path.GetFileName (pathToFile); this.className = filename.Replace ('.', '_'); // Overridden by @ Page classname this.className = className.Replace ('-', '_'); this.className = className.Replace (' ', '_'); Options ["ClassName"] = this.className; this.fullPath = Path.GetFullPath (pathToFile); this.has_form_tag = false; AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation; privateBinPath = setup.PrivateBinPath; // This is a hack until we can run stuff in different domains if (privateBinPath == null || privateBinPath.Length == 0) privateBinPath = "bin"; if (!Path.IsPathRooted (privateBinPath)) { string appbase = setup.ApplicationBase; if (appbase.StartsWith ("file://")) appbase = appbase.Substring (7); privateBinPath = Path.Combine (appbase, privateBinPath); } Init (); } public string BaseType { get { return basetype; } set { if (parent == null) parent = value; basetype = value; isUserControl = (basetype == "System.Web.UI.UserControl"); isPage = (basetype == "System.Web.UI.Page"); isApplication = (basetype == "System.Web.HttpApplication"); } } public bool IsUserControl { get { return isUserControl; } } public bool IsPage { get { return isPage; } } public bool IsApplication { get { return isApplication; } } public string Interfaces { get { return interfaces; } } public Hashtable Options { get { if (options == null) options = new Hashtable (); return options; } } public ArrayList Dependencies { get { return dependencies; } } internal HttpContext Context { get { return context; } set { context = value; } } bool AddUsing (string nspace) { string _using = "using " + nspace + ";"; if (prolog.ToString ().IndexOf (_using) == -1) { prolog.AppendFormat ("\t{0}\n", _using); return true; } return false; } void AddInterface (Type type) { AddInterface (type.ToString ()); } public void AddInterface (string iface) { if (interfaces == null) { interfaces = ", " + iface; } else { string s = ", " + iface; if (interfaces.IndexOf (s) == -1) interfaces += s; } } private AspComponentFoundry Foundry { get { if (aspFoundry == null) aspFoundry = new AspComponentFoundry (); return aspFoundry; } } private void Init () { dependencies = new ArrayList (); dependencies.Add (fullPath); controls = new ControlStack (); controls.Push (typeof (System.Web.UI.Control), "Root", null, ChildrenKind.CONTROLS, null); prolog = new StringBuilder (); declarations = new StringBuilder (); script = new StringBuilder (); constructor = new StringBuilder (); init_funcs = new StringBuilder (); epilog = new StringBuilder (); textChunk = new StringBuilder (); current_function = new StringBuilder (); functions = new Stack (); functions.Push (current_function); parts = new Object [6]; parts [0] = prolog; parts [1] = declarations; parts [2] = script; parts [3] = constructor; parts [4] = init_funcs; parts [5] = epilog; prolog.Append ("namespace ASP {\n" + "\tusing System;\n" + "\tusing System.Collections;\n" + "\tusing System.Collections.Specialized;\n" + "\tusing System.Configuration;\n" + "\tusing System.IO;\n" + "\tusing System.Text;\n" + "\tusing System.Text.RegularExpressions;\n" + "\tusing System.Web;\n" + "\tusing System.Web.Caching;\n" + "\tusing System.Web.Security;\n" + "\tusing System.Web.SessionState;\n" + "\tusing System.Web.UI;\n" + "\tusing System.Web.UI.WebControls;\n" + "\tusing System.Web.UI.HtmlControls;\n"); declarations.Append ("\t\tprivate static int __autoHandlers;\n"); current_function.Append ("\t\tprivate void __BuildControlTree (System.Web.UI.Control __ctrl)\n\t\t{\n"); if (!IsUserControl) current_function.Append ("\t\t\tSystem.Web.UI.IParserAccessor __parser = " + "(System.Web.UI.IParserAccessor) __ctrl;\n\n"); else controls.UseCodeRender = true; } public TextReader GetCode () { if (!parse_ok) throw new InvalidOperationException ("Parsing not done yet! (may be there were errors?)"); StringBuilder code = new StringBuilder (); for (int i = 0; i < parts.Length; i++) code.Append ((StringBuilder) parts [i]); return new StringReader (code.ToString ()); } // Regex.Escape () make some illegal escape sequences for a C# source. private string Escape (string input) { if (input == null) return String.Empty; string output = input.Replace ("\\", "\\\\"); output = output.Replace ("\"", "\\\""); output = output.Replace ("\t", "\\t"); output = output.Replace ("\r", "\\r"); output = output.Replace ("\n", "\\n"); output = output.Replace ("\n", "\\n"); return output; } bool AddProtectedField (Type type, string fieldName) { if (parentType == null) { declarations.AppendFormat ("\t\tprotected {0} {1};\n", type.ToString (), fieldName); return true; } FieldInfo field = parentType.GetField (fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); if (field == null || (!field.IsPublic && !field.IsFamily)) { declarations.AppendFormat ("\t\tprotected {0} {1};\n", type.ToString (), fieldName); return true; } if (!field.FieldType.IsAssignableFrom (type)) { string message = String.Format ("The base class includes the field '{0}', but its " + "type '{1}' is not compatible with {2}", fieldName, field.FieldType, type); throw new ApplicationException (message); } return false; } private Type LoadParentType (string typeName) { // First try loaded assemblies, then try assemblies in Bin directory. // By now i do this 'by hand' but may be this is a runtime/gac task. Type type = null; Assembly [] assemblies = AppDomain.CurrentDomain.GetAssemblies (); foreach (Assembly ass in assemblies) { type = ass.GetType (typeName); if (type != null) return type; } Assembly assembly; string [] binDlls = Directory.GetFiles (privateBinPath, "*.dll"); foreach (string dll in binDlls) { string dllPath = Path.Combine (privateBinPath, dll); assembly = null; try { assembly = Assembly.LoadFrom (dllPath); type = assembly.GetType (typeName); } catch (Exception e) { if (assembly != null) { Console.WriteLine ("ASP.NET Warning: assembly {0} loaded", dllPath); Console.WriteLine ("ASP.NET Warning: but type {0} not found", typeName); } else { Console.WriteLine ("ASP.NET Warning: unable to load type {0} from {1}", typeName, dllPath); } Console.WriteLine ("ASP.NET Warning: error was: {0}", e.Message); } if (type != null) { dependencies.Add (dllPath); return type; } } return null; } private void PageDirective (TagAttributes att) { if (att ["ClassName"] != null){ this.className = (string) att ["ClassName"]; Options ["ClassName"] = className; } if (att ["EnableSessionState"] != null){ if (!IsPage) throw new ApplicationException ("EnableSessionState not allowed here."); string est = (string) att ["EnableSessionState"]; if (0 == String.Compare (est, "false", true)) sessionState = SessionState.Disabled; else if (0 == String.Compare (est, "true", true)) sessionState = SessionState.Enabled; else if (0 == String.Compare (est, "readonly", true)) sessionState = SessionState.ReadOnly; else throw new ApplicationException ("EnableSessionState in Page directive not set to " + "a correct value: " + est); } if (att ["Inherits"] != null) { parent = (string) att ["Inherits"]; parentType = LoadParentType (parent); if (parentType == null) throw new ApplicationException ("The class " + parent + " cannot be found."); } if (att ["CompilerOptions"] != null) Options ["CompilerOptions"] = (string) att ["CompilerOptions"]; if (att ["AutoEventWireup"] != null) { if (options ["AutoEventWireup"] != null) throw new ApplicationException ("Already have an AutoEventWireup attribute"); bool autoevent = true; string v = att ["AutoEventWireup"] as string; try { autoevent = Convert.ToBoolean (v); } catch (Exception) { throw new ApplicationException ("'" + v + "' is not a valid value for AutoEventWireup"); } options ["AutoEventWireup"] = autoevent; } //FIXME: add support for more attributes. } void AddReference (string dll) { string references = Options ["References"] as string; if (references == null) references = dll; else references = references + "|" + dll; Options ["References"] = references; } private void RegisterDirective (TagAttributes att) { string tag_prefix = (string) (att ["tagprefix"] == null ? "" : att ["tagprefix"]); string name_space = (string) (att ["namespace"] == null ? "" : att ["namespace"]); string assembly_name = (string) (att ["assembly"] == null ? "" : att ["assembly"]); string tag_name = (string) (att ["tagname"] == null ? "" : att ["tagname"]); string src = (string) (att ["src"] == null ? "" : att ["src"]); if (tag_prefix != "" && name_space != "" && assembly_name != ""){ if (tag_name != "" || src != "") throw new ApplicationException ("Invalid attributes for @ Register: " + att.ToString ()); AddUsing (name_space); string dll = Path.Combine (privateBinPath, assembly_name + ".dll"); // Hack: it should use assembly.load semantics... // may be when we don't run mcs as a external program... if (!File.Exists (dll)) dll = assembly_name; else dependencies.Add (dll); Foundry.RegisterFoundry (tag_prefix, dll, name_space); AddReference (dll); return; } if (tag_prefix != "" && tag_name != "" && src != ""){ if (name_space != "" && assembly_name != "") throw new ApplicationException ("Invalid attributes for @ Register: " + att.ToString ()); if (!src.EndsWith (".ascx")) throw new ApplicationException ("Source file extension for controls " + "must be .ascx"); UserControlData data = GenerateUserControl (src, Context); switch (data.result) { case UserControlResult.OK: AddUsing ("ASP"); Foundry.RegisterFoundry (tag_prefix, tag_name, data.assemblyName, "ASP", data.className); AddReference (data.assemblyName); break; case UserControlResult.FileNotFound: throw new ApplicationException ("File '" + src + "' not found."); case UserControlResult.CompilationFailed: //TODO: should say where the generated .cs file is for the server to //show the source and the compiler error throw new NotImplementedException (); } return; } throw new ApplicationException ("Invalid combination of attributes in " + "@ Register: " + att.ToString ()); } private void ProcessDirective () { FlushPlainText (); Directive directive = (Directive) current; TagAttributes att = directive.Attributes; if (att == null) return; string value; string id = directive.TagID.ToUpper (); switch (id){ case "APPLICATION": if (main_directive != null) throw new ApplicationException (id + " not allowed after " + main_directive); if (!IsApplication) throw new ApplicationException ("@Application not allowed."); string inherits = att ["inherits"] as string; if (inherits != null) Options ["Inherits"] = inherits; main_directive = directive.TagID; break; case "PAGE": case "CONTROL": if (main_directive != null) throw new ApplicationException (id + " not allowed after " + main_directive); if (IsUserControl && id != "CONTROL") throw new ApplicationException ("@Page not allowed for user controls."); else if (IsPage && id != "PAGE") throw new ApplicationException ("@Control not allowed here. This is a page!"); PageDirective (att); main_directive = directive.TagID; break; case "IMPORT": value = att ["namespace"] as string; if (value == null || att.Count > 1) throw new ApplicationException ("Wrong syntax in Import directive."); string _using = "using " + value + ";"; if (AddUsing (value) == true) { string imports = Options ["Import"] as string; if (imports == null) { imports = value; } else { imports += "," + value; } Options ["Import"] = imports; } break; case "IMPLEMENTS": if (IsApplication) throw new ApplicationException ("@ Implements not allowed in an application file."); string iface = (string) att ["interface"]; AddInterface (iface); break; case "REGISTER": if (IsApplication) throw new ApplicationException ("@ Register not allowed in an application file."); RegisterDirective (att); break; case "ASSEMBLY": if (att.Count > 1) throw new ApplicationException ("Wrong syntax in Assembly directive."); string name = att ["name"] as string; string src = att ["src"] as string; if (name == null && src == null) throw new ApplicationException ("Wrong syntax in Assembly directive."); if (IsApplication && src != null) throw new ApplicationException ("'name' attribute expected."); value = (name == null) ? src : name; string assemblies = Options ["Assembly"] as string; if (assemblies == null) { assemblies = value; } else { assemblies += "," + value; } Options ["Assembly"] = assemblies; break; } } private void ProcessPlainText () { PlainText asis = (PlainText) current; string trimmed = asis.Text.Trim (); if (trimmed == String.Empty && controls.SpaceBetweenTags == true) return; if (IsApplication) { if (trimmed != String.Empty) throw new ApplicationException (app_file_wrong); return; } if (trimmed != String.Empty && controls.PeekChildKind () != ChildrenKind.CONTROLS){ string tag_id = controls.PeekTagID (); throw new ApplicationException ("Literal content not allowed for " + tag_id); } string escaped_text = Escape (asis.Text); current_function.AppendFormat ("\t\t\t__parser.AddParsedSubObject (" + "new System.Web.UI.LiteralControl (\"{0}\"));\n", escaped_text); StringBuilder codeRenderFunction = controls.CodeRenderFunction; codeRenderFunction.AppendFormat ("\t\t\t__output.Write (\"{0}\");\n", escaped_text); } private string EnumValueNameToString (Type enum_type, string value_name) { if (value_name.EndsWith ("*")) throw new ApplicationException ("Invalid property value: '" + value_name + ". It must be a valid " + enum_type.ToString () + " value."); MemberInfo [] nested_types = enum_type.FindMembers (MemberTypes.Field, BindingFlags.Public | BindingFlags.Static, Type.FilterNameIgnoreCase, value_name); if (nested_types.Length == 0) throw new ApplicationException ("Value " + value_name + " not found in enumeration " + enum_type.ToString ()); if (nested_types.Length > 1) throw new ApplicationException ("Value " + value_name + " found " + nested_types.Length + " in enumeration " + enum_type.ToString ()); return enum_type.ToString () + "." + nested_types [0].Name; } private void NewControlFunction (string tag_id, string control_id, Type control_type, ChildrenKind children_kind, string defaultPropertyName) { ChildrenKind prev_children_kind = controls.PeekChildKind (); if (prev_children_kind == ChildrenKind.NONE || prev_children_kind == ChildrenKind.PROPERTIES){ string prev_tag_id = controls.PeekTagID (); throw new ApplicationException ("Child controls not allowed for " + prev_tag_id); } Type allowed = null; if (prev_children_kind == ChildrenKind.DBCOLUMNS && !typeof (DataGridColumn).IsAssignableFrom (control_type)) { allowed = typeof (DataGridColumn); } else if (prev_children_kind == ChildrenKind.LISTITEM && control_type != typeof (System.Web.UI.WebControls.ListItem)) { allowed = typeof (DataGridColumn); } else if (prev_children_kind == ChildrenKind.HTMLROW) { Type prevType = controls.PeekType (); if (prevType == typeof (HtmlTable) && control_type != typeof (HtmlTableRow)) allowed = typeof (HtmlTableRow); else if (prevType == typeof (Table) && control_type != typeof (TableRow)) allowed = typeof (TableRow); } else if (prev_children_kind == ChildrenKind.HTMLCELL) { Type prevType = controls.PeekType (); if (prevType == typeof (HtmlTableRow) && control_type != typeof (HtmlTableCell)) allowed = typeof (HtmlTableCell); else if (prevType == typeof (TableRow) && control_type != typeof (TableCell)) allowed = typeof (TableCell); } if (allowed != null) throw new ApplicationException ("Inside " + controls.PeekTagID () + " only " + allowed + " objects are allowed"); StringBuilder func_code = new StringBuilder (); current_function = func_code; if (0 == String.Compare (tag_id, "form", true)){ if (has_form_tag) throw new ApplicationException ("Only one form server tag allowed."); has_form_tag = true; } controls.Push (control_type, control_id, tag_id, children_kind, defaultPropertyName); bool is_generic = control_type == typeof (System.Web.UI.HtmlControls.HtmlGenericControl); functions.Push (current_function); if (control_type != typeof (System.Web.UI.WebControls.ListItem) && prev_children_kind != ChildrenKind.DBCOLUMNS) { current_function.AppendFormat ("\t\tprivate System.Web.UI.Control __BuildControl_" + "{0} ()\n\t\t{{\n\t\t\t{1} __ctrl;\n\n\t\t\t__ctrl" + " = new {1} ({2});\n\t\t\tthis.{0} = __ctrl;\n", control_id, control_type, (is_generic? "\"" + tag_id + "\"" : "")); } else { current_function.AppendFormat ("\t\tprivate void __BuildControl_{0} ()\n\t\t{{" + "\n\t\t\t{1} __ctrl;\n\t\t\t__ctrl = new {1} ();" + "\n\t\t\tthis.{0} = __ctrl;\n", control_id, control_type); } if (children_kind == ChildrenKind.CONTROLS || children_kind == ChildrenKind.OPTION) current_function.Append ("\t\t\tSystem.Web.UI.IParserAccessor __parser = " + "(System.Web.UI.IParserAccessor) __ctrl;\n"); } private void DataBoundProperty (Type target, string varName, string value) { if (value == "") throw new ApplicationException ("Empty data binding tag."); string control_id = controls.PeekControlID (); string control_type_string = controls.PeekType ().ToString (); StringBuilder db_function = controls.DataBindFunction; string container; if (controls.Container == null || !typeof (INamingContainer).IsAssignableFrom (controls.Container)) container = "System.Web.UI.Control"; else { container = controls.Container.ToString (); } if (db_function.Length == 0) db_function.AppendFormat ("\t\tpublic void __DataBind_{0} (object sender, " + "System.EventArgs e) {{\n" + "\t\t\t{1} Container;\n" + "\t\t\t{2} target;\n" + "\t\t\ttarget = ({2}) sender;\n" + "\t\t\tContainer = ({1}) target.BindingContainer;\n", control_id, container, control_type_string); /* Removes '<%#' and '%>' */ string real_value = value.Remove (0,3); real_value = real_value.Remove (real_value.Length - 2, 2); real_value = real_value.Trim (); if (target == typeof (string)) db_function.AppendFormat ("\t\t\ttarget.{0} = System.Convert.ToString ({1});\n", varName, real_value); else db_function.AppendFormat ("\t\t\ttarget.{0} = ({1}) ({2});\n", varName, target, real_value); } /* * Returns true if it generates some code for the specified property */ private void AddCodeForPropertyOrField (Type type, string var_name, string att, bool isDataBound) { /* FIXME: should i check for this or let the compiler fail? * if (!prop.CanWrite) * .... */ if (isDataBound) { DataBoundProperty (type, var_name, att); } else if (type == typeof (string)){ if (att == null) throw new ApplicationException ("null value for attribute " + var_name ); current_function.AppendFormat ("\t\t\t__ctrl.{0} = \"{1}\";\n", var_name, Escape (att)); // FIXME: really Escape this? } else if (type.IsEnum){ if (att == null) throw new ApplicationException ("null value for attribute " + var_name ); string enum_value = EnumValueNameToString (type, att); current_function.AppendFormat ("\t\t\t__ctrl.{0} = {1};\n", var_name, enum_value); } else if (type == typeof (bool)){ string value; if (att == null) value = "true"; //FIXME: is this ok for non Style properties? else if (0 == String.Compare (att, "true", true)) value = "true"; else if (0 == String.Compare (att, "false", true)) value = "false"; else throw new ApplicationException ("Value '" + att + "' is not a valid boolean."); current_function.AppendFormat ("\t\t\t__ctrl.{0} = {1};\n", var_name, value); } else if (type == typeof (System.Web.UI.WebControls.Unit)){ //FIXME: should use the culture specified in Page try { Unit value = Unit.Parse (att, System.Globalization.CultureInfo.InvariantCulture); } catch (Exception) { throw new ApplicationException ("'" + att + "' cannot be parsed as a unit."); } current_function.AppendFormat ("\t\t\t__ctrl.{0} = " + "System.Web.UI.WebControls.Unit.Parse (\"{1}\", " + "System.Globalization.CultureInfo.InvariantCulture);\n", var_name, att); } else if (type == typeof (System.Web.UI.WebControls.FontUnit)){ //FIXME: should use the culture specified in Page try { FontUnit value = FontUnit.Parse (att, System.Globalization.CultureInfo.InvariantCulture); } catch (Exception) { throw new ApplicationException ("'" + att + "' cannot be parsed as a unit."); } current_function.AppendFormat ("\t\t\t__ctrl.{0} = " + "System.Web.UI.WebControls.FontUnit.Parse (\"{1}\", " + "System.Globalization.CultureInfo.InvariantCulture);\n", var_name, att); } else if (type == typeof (Int16) || type == typeof (Int32) || type == typeof (Int64)) { long value; try { value = Int64.Parse (att); //FIXME: should use the culture specified in Page } catch (Exception){ throw new ApplicationException (att + " is not a valid signed number " + "or is out of range."); } current_function.AppendFormat ("\t\t\t__ctrl.{0} = {1};\n", var_name, value); } else if (type == typeof (UInt16) || type == typeof (UInt32) || type == typeof (UInt64)) { ulong value; try { value = UInt64.Parse (att); //FIXME: should use the culture specified in Page } catch (Exception){ throw new ApplicationException (att + " is not a valid unsigned number " + "or is out of range."); } current_function.AppendFormat ("\t\t\t__ctrl.{0} = {1};\n", var_name, value); } else if (type == typeof (float)) { float value; try { value = Single.Parse (att); } catch (Exception){ throw new ApplicationException (att + " is not avalid float number or " + "is out of range."); } current_function.AppendFormat ("\t\t\t__ctrl.{0} = {1};\n", var_name, value); } else if (type == typeof (double)){ double value; try { value = Double.Parse (att); } catch (Exception){ throw new ApplicationException (att + " is not avalid double number or " + "is out of range."); } current_function.AppendFormat ("\t\t\t__ctrl.{0} = {1};\n", var_name, value); } else if (type == typeof (System.Drawing.Color)){ Color c; try { c = (Color) TypeDescriptor.GetConverter (typeof (Color)).ConvertFromString (att); } catch (Exception e){ throw new ApplicationException ("Color " + att + " is not a valid color.", e); } // Should i also test for IsSystemColor? // Are KnownColor members in System.Drawing.Color? if (c.IsKnownColor){ current_function.AppendFormat ("\t\t\t__ctrl.{0} = System.Drawing.Color." + "{1};\n", var_name, c.Name); } else { current_function.AppendFormat ("\t\t\t__ctrl.{0} = System.Drawing.Color." + "FromArgb ({1}, {2}, {3}, {4});\n", var_name, c.A, c.R, c.G, c.B); } } else if (type == typeof (string [])) { string [] subStrings = att.Split (','); current_function.AppendFormat ("\t\t\t__ctrl.{0} = new String [] {{\n", var_name); int end = subStrings.Length; for (int i = 0; i < end; i++) { string s = subStrings [i].Trim (); current_function.AppendFormat ("\t\t\t\t\"{0}\"", s); if (i == end - 1) current_function.Append ("\t\t\t\t};\n"); else current_function.Append (",\n"); } } else { throw new ApplicationException ("Unsupported type in property: " + type.ToString ()); } } private bool ProcessPropertiesAndFields (MemberInfo member, string id, TagAttributes att) { int hyphen = id.IndexOf ('-'); bool isPropertyInfo = (member is PropertyInfo); bool is_processed = false; bool isDataBound = att.IsDataBound ((string) att [id]); Type type; if (isPropertyInfo) { type = ((PropertyInfo) member).PropertyType; if (hyphen == -1 && ((PropertyInfo) member).CanWrite == false) return false; } else { type = ((FieldInfo) member).FieldType; } if (0 == String.Compare (member.Name, id, true)){ AddCodeForPropertyOrField (type, member.Name, (string) att [id], isDataBound); is_processed = true; } else if (hyphen != -1 && (type == fontinfoType || type == styleType || type.IsSubclassOf (styleType))){ string prop_field = id.Replace ("-", "."); string [] parts = prop_field.Split (new char [] {'.'}); if (parts.Length != 2 || 0 != String.Compare (member.Name, parts [0], true)) return false; PropertyInfo [] subprops = type.GetProperties (); foreach (PropertyInfo subprop in subprops){ if (0 != String.Compare (subprop.Name, parts [1], true)) continue; if (subprop.CanWrite == false) return false; bool is_bool = subprop.PropertyType == typeof (bool); if (!is_bool && att [id] == null){ att [id] = ""; // Font-Size -> Font-Size="" as html return false; } string value; if (att [id] == null && is_bool) value = "true"; // Font-Bold <=> Font-Bold="true" else value = (string) att [id]; AddCodeForPropertyOrField (subprop.PropertyType, member.Name + "." + subprop.Name, value, isDataBound); is_processed = true; } } return is_processed; } private void AddCodeForAttributes (Type type, TagAttributes att) { EventInfo [] ev_info = type.GetEvents (); PropertyInfo [] prop_info = type.GetProperties (); FieldInfo [] field_info = type.GetFields (); bool is_processed = false; ArrayList processed = new ArrayList (); foreach (string id in att.Keys){ if (0 == String.Compare (id, "runat", true) || 0 == String.Compare (id, "id", true)) continue; if (id.Length > 2 && id.Substring (0, 2).ToUpper () == "ON"){ string id_as_event = id.Substring (2); foreach (EventInfo ev in ev_info){ if (0 == String.Compare (ev.Name, id_as_event, true)){ current_function.AppendFormat ( "\t\t\t__ctrl.{0} += " + "new {1} (this.{2});\n", ev.Name, ev.EventHandlerType, att [id]); is_processed = true; break; } } if (is_processed){ is_processed = false; continue; } } foreach (PropertyInfo prop in prop_info){ is_processed = ProcessPropertiesAndFields (prop, id, att); if (is_processed) break; } if (!is_processed) { foreach (FieldInfo field in field_info){ is_processed = ProcessPropertiesAndFields (field, id, att); if (is_processed) break; } } if (is_processed){ is_processed = false; continue; } current_function.AppendFormat ("\t\t\t((System.Web.UI.IAttributeAccessor) __ctrl)." + "SetAttribute (\"{0}\", \"{1}\");\n", id, Escape ((string) att [id])); } } private void AddCodeRenderControl (StringBuilder function) { AddCodeRenderControl (function, controls.CodeRenderIndex); } private void AddCodeRenderControl (StringBuilder function, int index) { function.AppendFormat ("\t\t\tparameterContainer.Controls [{0}]." + "RenderControl (__output);\n", index); } private void AddRenderMethodDelegate (StringBuilder function, string control_id) { function.AppendFormat ("\t\t\t__ctrl.SetRenderMethodDelegate (new System.Web." + "UI.RenderMethod (this.__Render_{0}));\n", control_id); } private void AddCodeRenderFunction (string codeRender, string control_id) { StringBuilder codeRenderFunction = new StringBuilder (); codeRenderFunction.AppendFormat ("\t\tprivate void __Render_{0} " + "(System.Web.UI.HtmlTextWriter __output, " + "System.Web.UI.Control parameterContainer)\n" + "\t\t{{\n", control_id); codeRenderFunction.Append (codeRender); codeRenderFunction.Append ("\t\t}\n\n"); init_funcs.Append (codeRenderFunction); } private void RemoveLiterals (StringBuilder function) { string no_literals = Regex.Replace (function.ToString (), @"\t\t\t__parser.AddParsedSubObject \(" + @"new System.Web.UI.LiteralControl \(.+\);\n", ""); function.Length = 0; function.Append (no_literals); } private bool FinishControlFunction (string tag_id) { if (functions.Count == 0) throw new ApplicationException ("Unbalanced open/close tags"); if (controls.Count == 0) return false; string saved_id = controls.PeekTagID (); if (0 != String.Compare (saved_id, tag_id, true)) return false; FlushPlainText (); StringBuilder old_function = (StringBuilder) functions.Pop (); current_function = (StringBuilder) functions.Peek (); string control_id = controls.PeekControlID (); Type control_type = controls.PeekType (); ChildrenKind child_kind = controls.PeekChildKind (); bool hasDataBindFunction = controls.HasDataBindFunction (); if (hasDataBindFunction) old_function.AppendFormat ("\t\t\t__ctrl.DataBinding += new System.EventHandler " + "(this.__DataBind_{0});\n", control_id); bool useCodeRender = controls.UseCodeRender; if (useCodeRender) AddRenderMethodDelegate (old_function, control_id); if (control_type == typeof (System.Web.UI.ITemplate)){ old_function.Append ("\n\t\t}\n\n"); current_function.AppendFormat ("\t\t\t__ctrl.{0} = new System.Web.UI." + "CompiledTemplateBuilder (new System.Web.UI." + "BuildTemplateMethod (this.__BuildControl_{1}));\n", saved_id, control_id); } else if (control_type == typeof (System.Web.UI.WebControls.DataGridColumnCollection)){ old_function.Append ("\n\t\t}\n\n"); current_function.AppendFormat ("\t\t\tthis.__BuildControl_{0} (__ctrl.{1});\n", control_id, saved_id); } else if (control_type == typeof (System.Web.UI.WebControls.DataGridColumn) || control_type.IsSubclassOf (typeof (System.Web.UI.WebControls.DataGridColumn)) || control_type == typeof (System.Web.UI.WebControls.ListItem)){ old_function.Append ("\n\t\t}\n\n"); string parsed = ""; string ctrl_name = "ctrl"; Type cont = controls.Container; if (cont == null || cont == typeof (System.Web.UI.HtmlControls.HtmlSelect)){ parsed = "ParsedSubObject"; ctrl_name = "parser"; } current_function.AppendFormat ("\t\t\tthis.__BuildControl_{0} ();\n" + "\t\t\t__{1}.Add{2} (this.{0});\n\n", control_id, ctrl_name, parsed); } else if (child_kind == ChildrenKind.LISTITEM){ old_function.Append ("\n\t\t}\n\n"); init_funcs.Append (old_function); // Closes the BuildList function old_function = (StringBuilder) functions.Pop (); current_function = (StringBuilder) functions.Peek (); old_function.AppendFormat ("\n\t\t\tthis.__BuildControl_{0} (__ctrl.{1});\n\t\t\t" + "return __ctrl;\n\t\t}}\n\n", control_id, controls.PeekDefaultPropertyName ()); controls.Pop (); control_id = controls.PeekControlID (); current_function.AppendFormat ("\t\t\tthis.__BuildControl_{0} ();\n\t\t\t__parser." + "AddParsedSubObject (this.{0});\n\n", control_id); } else if (control_type == typeof (HtmlTableCell) || control_type == typeof (TableCell)) { old_function.Append ("\n\t\t\treturn __ctrl;\n\t\t}\n\n"); object top = controls.Pop (); Type t = controls.PeekType (); controls.Push (top); string parsed = ""; string ctrl_name = "ctrl"; if (!t.IsSubclassOf (typeof (WebControl)) && t != typeof (HtmlTableRow)) { parsed = "ParsedSubObject"; ctrl_name = "parser"; } current_function.AppendFormat ("\t\t\tthis.__BuildControl_{0} ();\n" + "\t\t\t__{1}.Add{2} (this.{0});\n\n", control_id, ctrl_name, parsed); } else if (child_kind == ChildrenKind.HTMLROW || child_kind == ChildrenKind.HTMLCELL) { old_function.Append ("\n\t\t}\n\n"); init_funcs.Append (old_function); old_function = (StringBuilder) functions.Pop (); current_function = (StringBuilder) functions.Peek (); old_function.AppendFormat ("\n\t\t\tthis.__BuildControl_{0} (__ctrl.{1});\n\t\t\t" + "return __ctrl;\n\t\t}}\n\n", control_id, controls.PeekDefaultPropertyName ()); controls.Pop (); control_id = controls.PeekControlID (); current_function.AppendFormat ("\t\t\tthis.__BuildControl_{0} ();\n", control_id); if (child_kind == ChildrenKind.HTMLROW) { current_function.AppendFormat ("\t\t\t__parser.AddParsedSubObject ({0});\n", control_id); } else { current_function.AppendFormat ("\t\t\t__ctrl.Add (this.{0});\n", control_id); } } else { old_function.Append ("\n\t\t\treturn __ctrl;\n\t\t}\n\n"); current_function.AppendFormat ("\t\t\tthis.__BuildControl_{0} ();\n\t\t\t__parser." + "AddParsedSubObject (this.{0});\n\n", control_id); } if (useCodeRender) RemoveLiterals (old_function); init_funcs.Append (old_function); if (useCodeRender) AddCodeRenderFunction (controls.CodeRenderFunction.ToString (), control_id); if (hasDataBindFunction){ StringBuilder db_function = controls.DataBindFunction; db_function.Append ("\t\t}\n\n"); init_funcs.Append (db_function); } // Avoid getting empty stacks for unbalanced open/close tags if (controls.Count > 1){ controls.Pop (); AddCodeRenderControl (controls.CodeRenderFunction, controls.ChildIndex); } return true; } private void NewTableElementFunction (HtmlControlTag ctrl) { string control_id = Tag.GetDefaultID (); ChildrenKind child_kind; Type t; if (ctrl.ControlType == typeof (HtmlTable)) { t = typeof (HtmlTableRowCollection); child_kind = ChildrenKind.HTMLROW; } else { t = typeof (HtmlTableCellCollection); child_kind = ChildrenKind.HTMLCELL; } controls.Push (ctrl.ControlType, control_id, ctrl.TagID, child_kind, ctrl.ParseChildren); current_function = new StringBuilder (); functions.Push (current_function); current_function.AppendFormat ("\t\tprivate void __BuildControl_{0} ({1} __ctrl)\n" + "\t\t{{\n", control_id, t); } private void ProcessHtmlControlTag () { FlushPlainText (); HtmlControlTag html_ctrl = (HtmlControlTag) current; if (html_ctrl.TagID.ToUpper () == "SCRIPT"){ //FIXME: if the is script is to be read from disk, do it! if (html_ctrl.SelfClosing) throw new ApplicationException ("Read script from file not supported yet."); sstatus = ScriptStatus.Open; return; } if (IsApplication) throw new ApplicationException (app_file_wrong); Type controlType = html_ctrl.ControlType; AddProtectedField (controlType, html_ctrl.ControlID); ChildrenKind children_kind; if (0 == String.Compare (html_ctrl.TagID, "table", true)) children_kind = ChildrenKind.HTMLROW; else if (0 == String.Compare (html_ctrl.TagID, "tr", true)) children_kind = ChildrenKind.HTMLCELL; else if (0 != String.Compare (html_ctrl.TagID, "select", true)) children_kind = html_ctrl.IsContainer ? ChildrenKind.CONTROLS : ChildrenKind.NONE; else children_kind = ChildrenKind.OPTION; NewControlFunction (html_ctrl.TagID, html_ctrl.ControlID, controlType, children_kind, html_ctrl.ParseChildren); current_function.AppendFormat ("\t\t\t__ctrl.ID = \"{0}\";\n", html_ctrl.ControlID); AddCodeForAttributes (html_ctrl.ControlType, html_ctrl.Attributes); if (children_kind == ChildrenKind.HTMLROW || children_kind == ChildrenKind.HTMLCELL) NewTableElementFunction (html_ctrl); if (html_ctrl.SelfClosing) FinishControlFunction (html_ctrl.TagID); } // Closing is performed in FinishControlFunction () private void NewBuildListFunction (AspComponent component) { string control_id = Tag.GetDefaultID (); controls.Push (component.ComponentType, control_id, component.TagID, component.ChildrenKind, component.DefaultPropertyName); Type childType = component.DefaultPropertyType; if (childType == null) childType = typeof (ListItemCollection); current_function = new StringBuilder (); functions.Push (current_function); current_function.AppendFormat ("\t\tprivate void __BuildControl_{0} " + "({1} __ctrl)\n" + "\t\t{{\n", control_id, childType); } private void ProcessComponent () { FlushPlainText (); AspComponent component = (AspComponent) current; Type component_type = component.ComponentType; AddProtectedField (component_type, component.ControlID); NewControlFunction (component.TagID, component.ControlID, component_type, component.ChildrenKind, component.DefaultPropertyName); if (component_type == typeof (UserControl) || component_type.IsSubclassOf (typeof (System.Web.UI.UserControl))) current_function.Append ("\t\t\t__ctrl.InitializeAsUserControl (Page);\n"); if (component_type == typeof (Control) || component_type.IsSubclassOf (typeof (System.Web.UI.Control))) current_function.AppendFormat ("\t\t\t__ctrl.ID = \"{0}\";\n", component.ControlID); AddCodeForAttributes (component.ComponentType, component.Attributes); if (component.ChildrenKind == ChildrenKind.LISTITEM || component.DefaultPropertyType != null) NewBuildListFunction (component); if (component.SelfClosing) FinishControlFunction (component.TagID); } private void ProcessServerObjectTag () { FlushPlainText (); ServerObjectTag obj = (ServerObjectTag) current; declarations.AppendFormat ("\t\tprivate {0} cached{1};\n", obj.ObjectClass, obj.ObjectID); constructor.AppendFormat ("\n\t\tprivate {0} {1}\n\t\t{{\n\t\t\tget {{\n\t\t\t\t" + "if (this.cached{1} == null)\n\t\t\t\t\tthis.cached{1} = " + "new {0} ();\n\t\t\t\treturn cached{1};\n\t\t\t}}\n\t\t}}\n\n", obj.ObjectClass, obj.ObjectID); } // Creates a new function that sets the values of subproperties. private void NewStyleFunction (PropertyTag tag) { current_function = new StringBuilder (); string prop_id = tag.PropertyID; Type prop_type = tag.PropertyType; // begin function current_function.AppendFormat ("\t\tprivate void __BuildControl_{0} ({1} __ctrl)\n" + "\t\t{{\n", prop_id, prop_type); // Add property initialization code PropertyInfo [] subprop_info = prop_type.GetProperties (); TagAttributes att = tag.Attributes; string subprop_name = null; foreach (string id in att.Keys){ if (0 == String.Compare (id, "runat", true) || 0 == String.Compare (id, "id", true)) continue; bool is_processed = false; foreach (PropertyInfo subprop in subprop_info){ is_processed = ProcessPropertiesAndFields (subprop, id, att); if (is_processed){ subprop_name = subprop.Name; break; } } if (subprop_name == null) throw new ApplicationException ("Property " + tag.TagID + " does not have " + "a " + id + " subproperty."); } // Finish function current_function.Append ("\n\t\t}\n\n"); init_funcs.Append (current_function); current_function = (StringBuilder) functions.Peek (); current_function.AppendFormat ("\t\t\tthis.__BuildControl_{0} (__ctrl.{1});\n", prop_id, tag.PropertyName); if (!tag.SelfClosing){ // Next tag should be the closing tag controls.Push (null, null, null, ChildrenKind.NONE, null); waitClosing = tag.TagID; } } // This one just opens the function. Closing is performed in FinishControlFunction () private void NewTemplateFunction (PropertyTag tag) { /* * FIXME * This function does almost the same as NewControlFunction. * Consider merging. */ string prop_id = tag.PropertyID; Type prop_type = tag.PropertyType; string tag_id = tag.PropertyName; // Real property name used in FinishControlFunction controls.Push (prop_type, prop_id, tag_id, ChildrenKind.CONTROLS, null); current_function = new StringBuilder (); functions.Push (current_function); current_function.AppendFormat ("\t\tprivate void __BuildControl_{0} " + "(System.Web.UI.Control __ctrl)\n" + "\t\t{{\n" + "\t\t\tSystem.Web.UI.IParserAccessor __parser " + "= (System.Web.UI.IParserAccessor) __ctrl;\n" , prop_id); } // Closing is performed in FinishControlFunction () private void NewDBColumnFunction (PropertyTag tag) { /* * FIXME * This function also does almost the same as NewControlFunction. * Consider merging. */ string prop_id = tag.PropertyID; Type prop_type = tag.PropertyType; string tag_id = tag.PropertyName; // Real property name used in FinishControlFunction controls.Push (prop_type, prop_id, tag_id, ChildrenKind.DBCOLUMNS, null); current_function = new StringBuilder (); functions.Push (current_function); current_function.AppendFormat ("\t\tprivate void __BuildControl_{0} " + "(System.Web.UI.WebControls.DataGridColumnCollection __ctrl)\n" + "\t\t{{\n", prop_id); } private void NewPropertyFunction (PropertyTag tag) { FlushPlainText (); if (tag.PropertyType == typeof (System.Web.UI.WebControls.Style) || tag.PropertyType.IsSubclassOf (typeof (System.Web.UI.WebControls.Style))) NewStyleFunction (tag); else if (tag.PropertyType == typeof (System.Web.UI.ITemplate)) NewTemplateFunction (tag); else if (tag.PropertyType == typeof (System.Web.UI.WebControls.DataGridColumnCollection)) NewDBColumnFunction (tag); else throw new ApplicationException ("Other than Style and ITemplate not supported yet. " + tag.PropertyType); } private void ProcessHtmlTag () { Tag tag = (Tag) current; ChildrenKind child_kind = controls.PeekChildKind (); if (child_kind == ChildrenKind.NONE){ string tag_id = controls.PeekTagID (); throw new ApplicationException (tag + " not allowed inside " + tag_id); } if (child_kind == ChildrenKind.OPTION){ if (0 != String.Compare (tag.TagID, "option", true)) throw new ApplicationException ("Only