From: Daniel C. \(kzu\) <dca...@us...> - 2005-10-31 09:31:37
|
Update of /cvsroot/mvp-xml/Design/v2/src/CustomTools/XGen In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv29101/v2/src/CustomTools/XGen Added Files: ClassPicker.cs ClassPicker.resx XGenRunner.cs XGenTool.cs XmlSerializerGenerator.cs Log Message: Finished port to v2 of XGen and XsdGen. Removed XmlValidate as the new XML editor has built-in support for validation. --- NEW FILE: ClassPicker.cs --- using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Text; using EnvDTE; namespace Mvp.Xml.Design.CustomTools.XGen { internal class ClassPicker : System.Windows.Forms.Form { #region Designer Stuff private System.Windows.Forms.Label label1; private System.Windows.Forms.Button btnCancel; private System.Windows.Forms.Button btnAccept; private Mvp.Xml.Design.ListViewEx lstClasses; private System.Windows.Forms.TextBox txtEditor; private System.Windows.Forms.ColumnHeader colClass; private System.Windows.Forms.ColumnHeader colTargetNs; /// <summary> /// Required designer variable. /// </summary> private System.ComponentModel.Container components = null; public ClassPicker() { // // Required for Windows Form Designer support // InitializeComponent(); // // TODO: Add any constructor code after InitializeComponent call // } /// <summary> /// Clean up any resources being used. /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { this.label1 = new System.Windows.Forms.Label(); this.btnCancel = new System.Windows.Forms.Button(); this.btnAccept = new System.Windows.Forms.Button(); this.lstClasses = new Mvp.Xml.Design.ListViewEx(); this.colClass = new System.Windows.Forms.ColumnHeader(); this.colTargetNs = new System.Windows.Forms.ColumnHeader(); this.txtEditor = new System.Windows.Forms.TextBox(); this.SuspendLayout(); // // label1 // this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.label1.Location = new System.Drawing.Point(8, 8); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(624, 48); this.label1.TabIndex = 1; this.label1.Text = "Select the classes you will use as the root of a deserialization process. A custo" + "m XmlSerializer and supporting classes will be generated for each of the selecte" + "d ones:"; // // btnCancel // this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; this.btnCancel.Location = new System.Drawing.Point(395, 280); this.btnCancel.Name = "btnCancel"; this.btnCancel.Size = new System.Drawing.Size(75, 23); this.btnCancel.TabIndex = 2; this.btnCancel.Text = "&Cancel"; this.btnCancel.Visible = false; // // btnAccept // this.btnAccept.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnAccept.DialogResult = System.Windows.Forms.DialogResult.OK; this.btnAccept.Location = new System.Drawing.Point(557, 280); this.btnAccept.Name = "btnAccept"; this.btnAccept.Size = new System.Drawing.Size(75, 23); this.btnAccept.TabIndex = 3; this.btnAccept.Text = "&OK"; // // lstClasses // this.lstClasses.AllowColumnReorder = true; this.lstClasses.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.lstClasses.CheckBoxes = true; this.lstClasses.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { this.colClass, this.colTargetNs}); this.lstClasses.DoubleClickActivation = false; this.lstClasses.FullRowSelect = true; this.lstClasses.Location = new System.Drawing.Point(8, 56); this.lstClasses.Name = "lstClasses"; this.lstClasses.Size = new System.Drawing.Size(624, 216); this.lstClasses.Sorting = System.Windows.Forms.SortOrder.Ascending; this.lstClasses.TabIndex = 0; this.lstClasses.UseCompatibleStateImageBehavior = false; this.lstClasses.View = System.Windows.Forms.View.Details; this.lstClasses.SubItemClicked += new Mvp.Xml.Design.SubItemEventHandler(this.lstClasses_SubItemClicked); // // colClass // this.colClass.Text = "Class"; this.colClass.Width = 344; // // colTargetNs // this.colTargetNs.Text = "Target Namespace"; this.colTargetNs.Width = 273; // // txtEditor // this.txtEditor.Location = new System.Drawing.Point(12, 280); this.txtEditor.Name = "txtEditor"; this.txtEditor.Size = new System.Drawing.Size(100, 20); this.txtEditor.TabIndex = 4; this.txtEditor.Visible = false; // // ClassPicker // this.AcceptButton = this.btnAccept; this.CancelButton = this.btnCancel; this.ClientSize = new System.Drawing.Size(640, 309); this.Controls.Add(this.txtEditor); this.Controls.Add(this.lstClasses); this.Controls.Add(this.btnAccept); this.Controls.Add(this.btnCancel); this.Controls.Add(this.label1); this.Name = "ClassPicker"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = " Class Picker"; this.ResumeLayout(false); this.PerformLayout(); } #endregion #endregion Designer Stuff string defaultNs; public ClassPicker(CodeElements elements, string defaultNs, SelectionCollection selections) { InitializeComponent(); // Add all classes. this.defaultNs = defaultNs; IterateElements(elements); if (lstClasses.Items.Count == 1) { lstClasses.Items[0].Checked = true; } if (selections != null) { foreach (Selection sel in selections) { foreach (ListViewItem item in lstClasses.Items) { if (item.Text == sel.ClassName) { item.Checked = true; } } } } } private void IterateElements(CodeElements elements) { foreach (CodeElement element in elements) { if (element is CodeNamespace) { IterateElements(((CodeNamespace)element).Members); } else if (element is CodeClass) { lstClasses.Items.Add(new ListViewItem( new string[] { ((CodeClass)element).FullName, this.defaultNs })); } } } public SelectionCollection Selections { get { SelectionCollection col = new SelectionCollection(); foreach (ListViewItem item in lstClasses.Items) { if (item.Checked) { col.Add(new Selection(item.Text, item.SubItems[1].Text)); } } return col; } } private void lstClasses_SubItemClicked(object sender, Mvp.Xml.Design.SubItemEventArgs e) { if (e.SubItem != 0) { lstClasses.StartEditing(txtEditor, e.Item, e.SubItem); } } } internal class SelectionCollection : CollectionBase { public static SelectionCollection FromString(string serializedData) { string[] items = serializedData.Split('|'); SelectionCollection col = new SelectionCollection(); foreach (string item in items) { col.Add(Selection.FromString(item)); } return col; } public int Add(Selection selection) { return base.InnerList.Add(selection); } public void Remove(Selection selection) { base.InnerList.Remove(selection); } public Selection this[int idx] { get { return (Selection)base.InnerList[idx]; } set { base.InnerList[idx] = value; } } public override string ToString() { string[] items = new string[base.Count]; for (int i = 0; i < items.Length; i++) { items[i] = base.InnerList[i].ToString(); } return String.Join("|", items); } } internal class Selection { public static Selection FromString(string serializedData) { string[] values = serializedData.Split(';'); return new Selection(values[0], values[1]); } public Selection(string className, string targetNs) { this.ClassName = className; this.TargetNamespace = targetNs; } public override string ToString() { return this.ClassName + ";" + this.TargetNamespace; } public string ClassName; public string TargetNamespace; } } --- NEW FILE: XGenRunner.cs --- using System; using System.IO; namespace Mvp.Xml.Design.CustomTools.XGen { /// <summary> /// Class that performs the actual code generation in the isolated design domain. /// </summary> internal class XGenRunner : MarshalByRefObject { /// <summary> /// Generates the code for the received type. /// </summary> public XGenRunner(string outputFile, string forType, string targetNamespace) { using (StreamWriter writer = new StreamWriter(outputFile)) { writer.Write(XmlSerializerGenerator.GenerateCode( Type.GetType(forType, true), targetNamespace)); } } } } --- NEW FILE: ClassPicker.resx --- <?xml version="1.0" encoding="utf-8"?> <root> <!-- Microsoft ResX Schema Version 2.0 The primary goals of this format is to allow a simple XML format that is mostly human readable. The generation and parsing of the various data types are done through the TypeConverter classes associated with the data types. Example: ... ado.net/XML headers & schema ... <resheader name="resmimetype">text/microsoft-resx</resheader> <resheader name="version">2.0</resheader> <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> <value>[base64 mime encoded serialized .NET Framework object]</value> </data> <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> <comment>This is a comment</comment> </data> There are any number of "resheader" rows that contain simple name/value pairs. Each data row contains a name, and value. The row also contains a type or mimetype. Type corresponds to a .NET class that support text/value conversion through the TypeConverter architecture. Classes that don't support this are serialized and stored with the mimetype set. The mimetype is used for serialized objects, and tells the ResXResourceReader how to depersist the object. This is currently not extensible. For a given mimetype the value must be set accordingly: Note - application/x-microsoft.net.object.binary.base64 is the format that the ResXResourceWriter will generate, however the reader can read any of the formats listed below. mimetype: application/x-microsoft.net.object.binary.base64 value : The object must be serialized with : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter : and then encoded with base64 encoding. mimetype: application/x-microsoft.net.object.soap.base64 value : The object must be serialized with : System.Runtime.Serialization.Formatters.Soap.SoapFormatter : and then encoded with base64 encoding. mimetype: application/x-microsoft.net.object.bytearray.base64 value : The object must be serialized into a byte array : using a System.ComponentModel.TypeConverter : and then encoded with base64 encoding. --> <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> <xsd:element name="root" msdata:IsDataSet="true"> <xsd:complexType> <xsd:choice maxOccurs="unbounded"> <xsd:element name="metadata"> <xsd:complexType> <xsd:sequence> <xsd:element name="value" type="xsd:string" minOccurs="0" /> </xsd:sequence> <xsd:attribute name="name" use="required" type="xsd:string" /> <xsd:attribute name="type" type="xsd:string" /> <xsd:attribute name="mimetype" type="xsd:string" /> <xsd:attribute ref="xml:space" /> </xsd:complexType> </xsd:element> <xsd:element name="assembly"> <xsd:complexType> <xsd:attribute name="alias" type="xsd:string" /> <xsd:attribute name="name" type="xsd:string" /> </xsd:complexType> </xsd:element> <xsd:element name="data"> <xsd:complexType> <xsd:sequence> <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> </xsd:sequence> <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> <xsd:attribute ref="xml:space" /> </xsd:complexType> </xsd:element> <xsd:element name="resheader"> <xsd:complexType> <xsd:sequence> <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> </xsd:sequence> <xsd:attribute name="name" type="xsd:string" use="required" /> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:complexType> </xsd:element> </xsd:schema> <resheader name="resmimetype"> <value>text/microsoft-resx</value> </resheader> <resheader name="version"> <value>2.0</value> </resheader> <resheader name="reader"> <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <resheader name="writer"> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> </root> --- NEW FILE: XGenTool.cs --- #region Usage /* Usage: * On any C# code file, set: * Custom Tool: XsdCodeGen * Custom Tool Namespace: [Optional override for default namespace that matches the C# file namespace] * * Author: Daniel Cazzulino - kz...@gm... */ #endregion Usage using System; using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Xml; using System.Xml.Schema; using EnvDTE; using VSLangProj; using System.Globalization; namespace Mvp.Xml.Design.CustomTools.XGen { /// <summary> /// Generates custom typed XmlSerializers. /// </summary> /// <remarks> /// On any class set the Custom Tool property to "Mvp.Xml.XGen". /// This tool supports C# projects only, as that's the code generated /// by the XmlSerializer class. /// </remarks> [Guid("B393315E-C463-41cd-A274-CEB18DB7073C")] [CustomTool("Mvp.Xml.XGen", "Mvp.Xml Project XmlSerializer Generation Tool", true)] [ComVisible(true)] [VersionSupport("8.0")] [CategorySupport(CategorySupportAttribute.CSharpCategory)] public class XGenTool : CustomTool { static string ConfigFile; const string XGenPrefix = "Mvp_Xml_XGen__"; #region Static config initialization static XGenTool() { AssemblyName name = Assembly.GetExecutingAssembly().GetName(); ConfigFile = Path.GetTempFileName() + ".config"; using (StreamWriter sw = new StreamWriter(ConfigFile)) { // Keep serialization files. Required for SGen to work. sw.Write(@"<?xml version='1.0' encoding='utf-8' ?> <configuration> <system.diagnostics> <switches> <add name='XmlSerialization.Compilation' value='4'/> </switches> </system.diagnostics> </configuration>"); } } #endregion Static config initialization #region GenerateCode /// <summary> /// Generates the output. /// </summary> protected override string OnGenerateCode(string inputFileName, string inputFileContent) { ThrowIfNoClassFound(); string outputPath = GetProjectOutputFullPath(); // Force compilation of the current project. We need the type in the output. CurrentItem.DTE.Solution.SolutionBuild.BuildProject( CurrentItem.DTE.Solution.SolutionBuild.ActiveConfiguration.Name, CurrentItem.ContainingProject.UniqueName, true); if (CurrentItem.DTE.Solution.SolutionBuild.LastBuildInfo == 1) { return Properties.Resources.XGenTool_ProjectDoesNotCompile; } CopyDesignToOutput(outputPath); SelectionCollection selections = GetSerializedSelection(); selections = ShowSelectionUI(selections); if (selections == null || selections.Count == 0) { return String.Empty; } StringBuilder output = new StringBuilder(); AppDomainSetup appSetup = new AppDomainSetup(); appSetup.ApplicationName = typeof(XGenTool).Namespace; appSetup.ApplicationBase = outputPath; appSetup.ConfigurationFile = ConfigFile; foreach (Selection selection in selections) { GetSerializationCode(appSetup, output, selection); } SaveSelections(selections); return output.ToString(); } private void SaveSelections(SelectionCollection selections) { string key = BuildItemKey(CurrentItem); CurrentItem.ContainingProject.Globals[key] = selections.ToString(); CurrentItem.ContainingProject.Globals.set_VariablePersists(key, true); } private void GetSerializationCode(AppDomainSetup appSetup, StringBuilder output, Selection selection) { string codefile = Path.GetTempFileName(); AppDomain domain = AppDomain.CreateDomain(appSetup.ApplicationName, null, appSetup); try { // Runner ctor will dump the output to the file we pass. domain.CreateInstance( Assembly.GetExecutingAssembly().FullName, typeof(XGenRunner).FullName, false, 0, null, new object[] { codefile, selection.ClassName + ", " + CurrentItem.ContainingProject.Properties.Item("AssemblyName").Value.ToString(), selection.TargetNamespace }, null, null, null); using (StreamReader reader = new StreamReader(codefile)) { output.Append(reader.ReadToEnd()).Append(Environment.NewLine); } } finally { AppDomain.Unload(domain); } } private SelectionCollection ShowSelectionUI(SelectionCollection selections) { ClassPicker picker = new ClassPicker(CurrentItem.FileCodeModel.CodeElements, base.FileNameSpace, selections); if (picker.ShowDialog() == System.Windows.Forms.DialogResult.OK) { selections = picker.Selections; } return selections; } private SelectionCollection GetSerializedSelection() { string key = BuildItemKey(CurrentItem); string serialized = String.Empty; if (CurrentItem.ContainingProject.Globals.get_VariableExists(key)) { serialized = (string)CurrentItem.ContainingProject.Globals[key]; } SelectionCollection selections = null; if (serialized.Length > 0) { selections = SelectionCollection.FromString(serialized); } return selections; } private void CopyDesignToOutput(string outputPath) { // Copy Design assembly to output for the isolated AppDomain. string asmfile = GetAssemblyPath(Assembly.GetExecutingAssembly()); try { File.Copy(asmfile, Path.Combine(outputPath, Path.GetFileName(asmfile)), true); } catch (Exception ex) { // May already exist, be locked, etc. System.Diagnostics.Debug.WriteLine(ex.ToString()); } } private void ThrowIfNoClassFound() { if (CurrentItem.FileCodeModel == null || CurrentItem.FileCodeModel.CodeElements == null) { throw new InvalidOperationException(Properties.Resources.XGenTool_NoClassFound); } } private string GetProjectOutputFullPath() { string outputPath = base.CurrentItem.ContainingProject.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath").Value.ToString(); outputPath = Path.Combine(base.CurrentItem.ContainingProject.Properties.Item("FullPath").Value.ToString(), outputPath); return outputPath; } private string BuildItemKey(ProjectItem item) { string itemVirtualPath = DteHelper.BuildPath(item); itemVirtualPath = itemVirtualPath.Replace(".", "__").Replace("\\", "___"); return itemVirtualPath; } private string GetAssemblyPath(Assembly assembly) { Uri uri = new Uri(assembly.CodeBase); return uri.LocalPath; } #endregion GenerateCode #region GetDefaultExtension /// <summary> /// This tool generates code, and the default extension equals that of the current code provider. /// </summary> public override string GetDefaultExtension() { return "Serialization." + base.CodeProvider.FileExtension; } #endregion GetDefaultExtension #region Registration and Installation /// <summary> /// Registers the generator. /// </summary> [ComRegisterFunction] public static void RegisterClass(Type type) { CustomTool.Register(typeof(XGenTool)); } /// <summary> /// Unregisters the generator. /// </summary> [ComUnregisterFunction] public static void UnregisterClass(Type t) { CustomTool.UnRegister(typeof(XGenTool)); } #endregion Registration and Installation } } --- NEW FILE: XmlSerializerGenerator.cs --- using System; using System.Collections; using System.CodeDom; using System.CodeDom.Compiler; using System.IO; using System.Reflection; using System.Text.RegularExpressions; using System.Xml.Serialization; namespace Mvp.Xml.Design.CustomTools.XGen { internal class XmlSerializerGenerator { #region Constants /// <summary> /// In the <see cref="PrivateReadMethods"/> regular expression, /// the return type of the read method. /// </summary> const string ReadMethodReturnType = "return"; /// <summary> /// In the <see cref="PrivateReadMethods"/> regular expression, /// full method signature excluding return type. /// </summary> const string ReadMethodFullSignature = "method"; /// <summary> /// In the <see cref="PrivateReadMethods"/> regular expression, /// matched method name. /// </summary> const string ReadMethodName = "name"; /// <summary> /// In the <see cref="PrivateReadMethods"/> regular expression, /// matched method arguments. /// </summary> const string ReadMethodArguments = "arguments"; /// <summary> /// In the <see cref="PublicRead"/> or <see cref="PublicWrite"/> regular expressions, /// matched name of the object being read or written. /// </summary> const string MethodObjectName = "object"; /// <summary> /// In the <see cref="PublicRead"/> or <see cref="PublicWrite"/> regular expressions, /// the name of the matched public read or write method. /// </summary> const string MethodName = "method"; /// <summary> /// In the <see cref="ClassDeclaration"/> regular expression, /// the matched name of the class being declared. /// </summary> const string ClassDeclarationName = "className"; /// <summary> /// In the <see cref="ClassDeclaration"/> regular expression, /// the matched name of the base class of the class being declared. /// </summary> const string ClassDeclarationBaseType = "baseClass"; #endregion #region Regular Expressions /// <summary> /// Matches all non-void methods called Read_[identifier]([...arguments...]) /// </summary> static Regex PrivateReadMethods = new Regex(@" # Returned type # (?<return>[^\s]+)\s # Method call start # (?<method> (?<name>Read\d+_ # Object being read # (?<object>[^\(]+))\( # Method arguments # (?<arguments>[^\)]+) # Method call end # \))\s{", RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.Multiline); /// <summary> /// Matches a public object Read_[object] method. /// </summary> static Regex PublicRead = new Regex(@"public object (?<method>Read\d+_(?<object>[^\(]+))", RegexOptions.Compiled | RegexOptions.Multiline); /// <summary> /// Matches a public void Write_[object] method. /// </summary> static Regex PublicWrite = new Regex(@"public void (?<method>Write\d+_(?<object>[^\(]+))", RegexOptions.Compiled | RegexOptions.Multiline); /// <summary> /// Matches an assembly level AssemblyVersionAttribute attribute declaration. /// </summary> static Regex AssemblyVersion = new Regex(@"\[assembly:System.Reflection.AssemblyVersionAttribute[^\]]*\]", RegexOptions.Compiled | RegexOptions.Multiline); /// <summary> /// Matches an identifier previous to a space or coma. /// </summary> static Regex RemoveTypeFromArguments = new Regex(@"[^\s,]+[\s]", RegexOptions.Compiled); /// <summary> /// Matches the full class declaration, capturing the class name and base type. /// </summary> static Regex ClassDeclaration = new Regex(@"public( abstract)?( sealed)? class (?<className>[^\s]+) :\s?(?<baseClass>[^\s]+)", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture); /// <summary> /// Matches only the "public [modifiers] class" part. Used for replacing the visibility. /// </summary> static Regex SimpleClassDeclaration = new Regex(@"public(?<modifiers>( abstract)?( sealed)?( partial)?) class", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture); #endregion #region Code templates /// <summary> /// Template for the override for each element being read. /// {0}=Return type /// {1}=Method name /// {2}=Delegate name /// {3}=Event name /// {4}=Arguments with type definition /// {5}=Arguments with no type /// </summary> const string TemplateMethodOverride = @" /// <remarks/> protected override {0} {1}({4}) {{ {0} obj = base.{1}({5}); {2} handler = {3}; if (handler != null) handler(obj); return obj; }}"; /// <summary> /// Template for the public Read method for the root object /// in the custom reader firing events. /// {0}=Return type /// {1}=Read method name generated by serializer for root object. /// </summary> const string TemplateReadMethod = @" /// <summary>Reads an object of type {0}.</summary> internal {0} Read() {{ return ({0}) {1}(); }}"; /// <summary> /// Template for the public Write method for the root object /// in the custom writer. /// {0}=Object type name /// {1}=Write method name generated by serializer for root object. /// </summary> const string TemplateWriteMethod = @" /// <summary>Writes an object of type {0}.</summary> internal void Write({0} value) {{ {1}(value); }}"; /// <summary> /// Template for custom serializer class members. /// {0}=Name of the serializer class /// {1}=Reader type name /// {2}=Writer type name /// {3}=Root object type /// </summary> const string TemplateCustomSerializer = @" {1} _reader; {2} _writer; /// <summary>Constructs the serializer.</summary> public {0}() {{ }} /// <summary>Constructs the serializer with a pre-built reader.</summary> public {0}({1} reader) {{ _reader = reader; }} /// <summary>Constructs the serializer with a pre-built writer.</summary> public {0}({2} writer) {{ _writer = writer; }} /// <summary>Constructs the serializer with pre-built reader and writer.</summary> public {0}({1} reader, {2} writer) {{ _reader = reader; _writer = writer; }} /// <remarks/> protected override System.Xml.Serialization.XmlSerializationReader CreateReader() {{ if (_reader != null) return _reader; return base.CreateReader(); }} /// <remarks/> protected override System.Xml.Serialization.XmlSerializationWriter CreateWriter() {{ if (_writer != null) return _writer; return base.CreateWriter(); }}"; #endregion Code templates #region Private helper methods public static string GenerateCode(Type type, string targetNamespace) { string output = GetXmlSerializerOutput(type); System.CodeDom.CodeNamespace ns = InitializeCodeDom(targetNamespace); output = CleanupXmlSerializerCode(targetNamespace, output); // Find the methods that are the entry points for reading and writing the object. Match readMatch = PublicRead.Match(output); Match writeMatch = PublicWrite.Match(output); string rootObject = readMatch.Groups[MethodObjectName].Value; output = MakeSerializerPartial(output, rootObject); output = RenameClasses(output, rootObject); output = NestTypesOnSerialier(output, rootObject); output = AppendReaderWriterMembersToObjectSerializer(type, output, rootObject); CodeTypeDeclaration readerDeclaration = CreateAndAddEventRisingReader(targetNamespace, output, ns, rootObject); output = PrivateReadMethodsToProtectedVirtual(output); CodeTypeDeclaration writerDeclaration = CreateAndAddTypedWriter(targetNamespace, ns, rootObject); // Renders TemplateReadMethod (see that constant declaration) output = AddTypedRead(readerDeclaration, type, output, readMatch); // Renders TemplateWriteMethod (see that constant declaration) output = AddTypedWrite(writerDeclaration, type, output, writeMatch); output = MergeOutputWithCodeDom(output, ns); output = MakeClassesInternal(output); output = NormalizeLineEndings(output); output = NestTypesOnSerialier(output, rootObject); output = FixInstantiationOfNestedTypes(output, rootObject); output = "\r\n#pragma warning disable 0642, 0219\r\n" + output + "\r\n#pragma warning restore 0642, 0219"; output = CustomTool.GetToolGeneratedCodeWarning(typeof(XGenTool)) + output; #if DEBUG return @"#region Original XmlSerializer code /* ------------------------------------------------------------------------------------- " + GetXmlSerializerOutput(type) + @" -------------------------------------------------------------------------------------*/ " + @"#endregion " + output; #else return output; #endif //"\r\n#pragma warning disable 0642, 0219\r\n" + endlines.ToString() + "#pragma warning restore 0642, 0219"; } private static string MakeSerializerPartial(string output, string rootObject) { string rootSerializer = rootObject + "Serializer"; return output.Replace("public sealed class " + rootSerializer, "public sealed partial class " + rootSerializer); } private static string FixInstantiationOfNestedTypes(string output, string rootObject) { output = output.Replace("new BaseWriter", "new " + rootObject + "Serializer.BaseWriter"); output = output.Replace("new BaseReader", "new " + rootObject + "Serializer.BaseReader"); return output; } private static string NormalizeLineEndings(string output) { // Sometimes VS doesn't like the line endings we got from the XmlSerializer code... StringWriter endlines = new StringWriter(); using (StringReader sr = new StringReader(output)) { string line; while ((line = sr.ReadLine()) != null) { endlines.WriteLine(line); } } output = endlines.ToString(); return output; } private static string MakeClassesInternal(string output) { // Make all classes internal instead of public. return SimpleClassDeclaration.Replace(output, "internal${modifiers} class"); } private static string MergeOutputWithCodeDom(string output, System.CodeDom.CodeNamespace ns) { CodeGeneratorOptions opt = new CodeGeneratorOptions(); opt.BracingStyle = "C"; StringWriter finalcode = new StringWriter(); new Microsoft.CSharp.CSharpCodeProvider().GenerateCodeFromNamespace(ns, finalcode, opt); finalcode.WriteLine(output); return finalcode.ToString(); } private static string NestTypesOnSerialier(string output, string rootObject) { string rootSerializer = rootObject + "Serializer"; // Nest public reader/writer classes first MatchCollection classMatches = ClassDeclaration.Matches(output); for (int i = classMatches.Count - 1; i >= 0; i--) { Match m = classMatches[i]; // Skip the serializer itself if (m.Groups[ClassDeclarationName].Value.StartsWith(rootObject)) { continue; } int closePosition = FindClosingBracketPosition(output, output.IndexOf('{', m.Index)); output = output.Insert(++closePosition, "\n\t}"); output = output.Insert(m.Index, "public partial class " + rootSerializer + " {\n\t"); } return output; } private static int FindClosingBracketPosition(string output, int openPosition) { int openCount = 0; for (int i = openPosition; i < output.Length; i++) { if (output[i] == '{') openCount++; if (output[i] == '}') openCount--; if (openCount == 0) return i; } throw new ArgumentException(Properties.Resources.XGenTool_UnclosedBrackets); } private static string PrivateReadMethodsToProtectedVirtual(string output) { // Turn all private methods into protected virtual, as they are overriden // by the custom event rising reader. output = PrivateReadMethods.Replace(output, @"/// <remarks/> protected virtual ${return} ${method} {"); return output; } private static CodeTypeDeclaration CreateAndAddTypedWriter(string targetNamespace, System.CodeDom.CodeNamespace ns, string rootObject) { CodeTypeDeclaration writerDeclaration = new CodeTypeDeclaration(rootObject + "Writer"); writerDeclaration.BaseTypes.Add(rootObject + "Serializer.BaseWriter"); writerDeclaration.Comments.Add(new CodeCommentStatement( "/// <summary>Custom writer for " + rootObject + " instances.</summary>", true)); ns.Types.Add(writerDeclaration); return writerDeclaration; } private static CodeTypeDeclaration CreateAndAddEventRisingReader(string targetNamespace, string output, System.CodeDom.CodeNamespace ns, string rootObject) { CodeTypeDeclaration readerDeclaration = new CodeTypeDeclaration(rootObject + "Reader"); readerDeclaration.BaseTypes.Add(rootObject + "Serializer.BaseReader"); readerDeclaration.Comments.Add(new CodeCommentStatement( "/// <summary>Custom reader for " + rootObject + " instances.</summary>", true)); ns.Types.Add(readerDeclaration); // For each read method Match readMethodMatch = PrivateReadMethods.Match(output); while (readMethodMatch.Success) { string objectBeingRead = readMethodMatch.Groups[MethodObjectName].Value; // Skip the generic reading method. if (objectBeingRead.ToLower() == "object") { readMethodMatch = readMethodMatch.NextMatch(); continue; } // Create a delegate for the event to expose. // public delegate void [objectBeingRead]DeserializedHandler(Mvp.Xml.Design.Tests.Customer customer); CodeTypeDelegate del = new CodeTypeDelegate(objectBeingRead + "DeserializedHandler"); del.Parameters.Add(new CodeParameterDeclarationExpression( readMethodMatch.Groups[ReadMethodReturnType].Value, MakeFirstLower(objectBeingRead))); del.Comments.Add(new CodeCommentStatement("/// <remarks/>", true)); ns.Types.Add(del); // Expose event. // public event [objectBeingRead]DeserializedHandler [objectBeingRead]Deserialized; CodeMemberEvent ev = new CodeMemberEvent(); ev.Name = objectBeingRead + "Deserialized"; ev.Attributes = MemberAttributes.Public; ev.Type = new CodeTypeReference(del.Name); ev.Comments.Add(new CodeCommentStatement("/// <remarks/>", true)); readerDeclaration.Members.Add(ev); // Override base method. string methodOverride = String.Format( TemplateMethodOverride, readMethodMatch.Groups[ReadMethodReturnType].Value, readMethodMatch.Groups[ReadMethodName].Value, del.Name, ev.Name, readMethodMatch.Groups[ReadMethodArguments].Value, // Arguments contain type + parameter name. Remove type for method call. RemoveTypeFromArguments.Replace( readMethodMatch.Groups[ReadMethodArguments].Value, "")); readerDeclaration.Members.Add(new CodeSnippetTypeMember(methodOverride)); readMethodMatch = readMethodMatch.NextMatch(); } return readerDeclaration; } private static string AddTypedRead(CodeTypeDeclaration reader, Type type, string output, Match readMatch) { // Add the "final" public read method. reader.Members.Add(new CodeSnippetTypeMember( String.Format(TemplateReadMethod, type.FullName, readMatch.Groups[MethodName].Value))); // Turn original public method into internal protected output = PublicRead.Replace(output, "protected internal object ${method}"); return output; } private static string AddTypedWrite(CodeTypeDeclaration writer, Type type, string output, Match writeMatch) { // Add the "final" public write method. writer.Members.Add(new CodeSnippetTypeMember( String.Format(TemplateWriteMethod, type.FullName, writeMatch.Groups[MethodName].Value))); // Turn original public method into internal protected output = PublicWrite.Replace(output, "protected internal void ${method}"); return output; } private static string MakeFirstLower(string value) { char[] name = value.ToCharArray(); name[0] = Char.ToLower(name[0]); return new string(name); } private static string AppendReaderWriterMembersToObjectSerializer(Type type, string output, string rootObject) { string readerWriterMembers = String.Format( TemplateCustomSerializer, rootObject + "Serializer", rootObject + "Reader", rootObject + "Writer", type.FullName); output = output.Replace(": " + rootObject + "BaseSerializer {", ": " + rootObject + "BaseSerializer {\n" + readerWriterMembers); return output; } private static string RenameClasses(string output, string rootObject) { // Give generated classes more friendly names. output = output.Replace("XmlSerializationWriter" + rootObject, "BaseWriter"); output = output.Replace("XmlSerializationReader" + rootObject, "BaseReader"); output = output.Replace("XmlSerializer1", rootObject + "SerializerBase"); return output; } private static string CleanupXmlSerializerCode(string targetNamespace, string output) { // Remove assembly version attribute. output = AssemblyVersion.Replace(output, ""); // Replace namespace. output = output.Replace("Microsoft.Xml.Serialization.GeneratedAssembly", targetNamespace); return output; } private static System.CodeDom.CodeNamespace InitializeCodeDom(string targetNamespace) { System.CodeDom.CodeNamespace ns = new System.CodeDom.CodeNamespace(targetNamespace); ns.Imports.Add(new CodeNamespaceImport("System.Xml.Serialization")); ns.Imports.Add(new CodeNamespaceImport("System")); // CodeTypeDeclaration serializer = new CodeTypeDeclaration(type.Name + "Serializer"); // serializer.BaseTypes.Add(typeof(XmlSerializer)); // serializer.Comments.Add(new CodeCommentStatement( // "<summary>Custom serializer for " + type.Name + " type.</summary", true)); // ns.Types.Add(serializer); return ns; } private static string GetXmlSerializerOutput(Type type) { string output; XmlSerializer ser = new XmlSerializer(type); // Here starts the horrible reflection hacks! //ser.tempAssembly.assembly.Location FieldInfo fitempasm = ser.GetType().GetField("tempAssembly", BindingFlags.NonPublic | BindingFlags.Instance); object tempasm = fitempasm.GetValue(ser); FieldInfo fiasm = tempasm.GetType().GetField("assembly", BindingFlags.NonPublic | BindingFlags.Instance); Assembly asm = (Assembly)fiasm.GetValue(tempasm); string codebase = new Uri(asm.CodeBase).LocalPath; string code = Path.Combine( Path.GetDirectoryName(codebase), Path.GetFileNameWithoutExtension(codebase) + ".0.cs"); string original; using (StreamReader sr = new StreamReader(code)) { output = sr.ReadToEnd(); original = output; } return output; } #endregion } } |