From 0358780c77469974dd8bc87840364e790ccecc29 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 15 Aug 2017 00:36:12 -0700 Subject: Migrate XsdGen --- src/WixBuildTools.XsdGen/AssemblyInfo.cs | 9 + src/WixBuildTools.XsdGen/CodeDomInterfaces.cs | 96 ++ src/WixBuildTools.XsdGen/CodeDomReader.cs | 159 +++ src/WixBuildTools.XsdGen/ElementCollection.cs | 642 +++++++++ src/WixBuildTools.XsdGen/StronglyTypedClasses.cs | 1498 ++++++++++++++++++++ .../WixBuildTools.XsdGen.csproj | 22 + src/WixBuildTools.XsdGen/XsdGen.cs | 124 ++ .../build/WixBuildTools.XsdGen.targets | 67 + .../WixBuildTools.XsdGen.targets | 6 + 9 files changed, 2623 insertions(+) create mode 100644 src/WixBuildTools.XsdGen/AssemblyInfo.cs create mode 100644 src/WixBuildTools.XsdGen/CodeDomInterfaces.cs create mode 100644 src/WixBuildTools.XsdGen/CodeDomReader.cs create mode 100644 src/WixBuildTools.XsdGen/ElementCollection.cs create mode 100644 src/WixBuildTools.XsdGen/StronglyTypedClasses.cs create mode 100644 src/WixBuildTools.XsdGen/WixBuildTools.XsdGen.csproj create mode 100644 src/WixBuildTools.XsdGen/XsdGen.cs create mode 100644 src/WixBuildTools.XsdGen/build/WixBuildTools.XsdGen.targets create mode 100644 src/WixBuildTools.XsdGen/buildCrossTargeting/WixBuildTools.XsdGen.targets (limited to 'src/WixBuildTools.XsdGen') diff --git a/src/WixBuildTools.XsdGen/AssemblyInfo.cs b/src/WixBuildTools.XsdGen/AssemblyInfo.cs new file mode 100644 index 00000000..b3740b2a --- /dev/null +++ b/src/WixBuildTools.XsdGen/AssemblyInfo.cs @@ -0,0 +1,9 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyCulture("")] +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] diff --git a/src/WixBuildTools.XsdGen/CodeDomInterfaces.cs b/src/WixBuildTools.XsdGen/CodeDomInterfaces.cs new file mode 100644 index 00000000..850839d4 --- /dev/null +++ b/src/WixBuildTools.XsdGen/CodeDomInterfaces.cs @@ -0,0 +1,96 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Serialize +{ + using System; + using System.Collections; + using System.Xml; + + /// + /// Interface for generated schema elements. + /// + public interface ISchemaElement + { + /// + /// Gets and sets the parent of this element. May be null. + /// + /// An ISchemaElement that has this element as a child. + ISchemaElement ParentElement + { + get; + set; + } + + /// + /// Outputs xml representing this element, including the associated attributes + /// and any nested elements. + /// + /// XmlTextWriter to be used when outputting the element. + void OutputXml(XmlWriter writer); + } + + /// + /// Interface for generated schema elements. Implemented by elements that have child + /// elements. + /// + public interface IParentElement + { + /// + /// Gets an enumerable collection of the children of this element. + /// + /// An enumerable collection of the children of this element. + IEnumerable Children + { + get; + } + + /// + /// Gets an enumerable collection of the children of this element, filtered + /// by the passed in type. + /// + /// The type of children to retrieve. + IEnumerable this[Type childType] + { + get; + } + + /// + /// Adds a child to this element. + /// + /// Child to add. + void AddChild(ISchemaElement child); + + /// + /// Removes a child from this element. + /// + /// Child to remove. + void RemoveChild(ISchemaElement child); + } + + /// + /// Interface for generated schema elements. Implemented by classes with attributes. + /// + public interface ISetAttributes + { + /// + /// Sets the attribute with the given name to the given value. The value here is + /// a string, and is converted to the strongly-typed version inside this method. + /// + /// The name of the attribute to set. + /// The value to assign to the attribute. + void SetAttribute(string name, string value); + } + + /// + /// Interface for generated schema elements. Implemented by classes with children. + /// + public interface ICreateChildren + { + /// + /// Creates an instance of the child with the passed in name. + /// + /// String matching the element name of the child when represented in XML. + /// An instance of that child. + ISchemaElement CreateChild(string childName); + } +} diff --git a/src/WixBuildTools.XsdGen/CodeDomReader.cs b/src/WixBuildTools.XsdGen/CodeDomReader.cs new file mode 100644 index 00000000..5198f264 --- /dev/null +++ b/src/WixBuildTools.XsdGen/CodeDomReader.cs @@ -0,0 +1,159 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Serialize +{ + using System; + using System.Collections; + using System.Reflection; + using System.Xml; + + /// + /// Class used for reading XML files in to the CodeDom. + /// + public class CodeDomReader + { + private Assembly[] assemblies; + + /// + /// Creates a new CodeDomReader, using the current assembly. + /// + public CodeDomReader() + { + this.assemblies = new Assembly[] { Assembly.GetExecutingAssembly() }; + } + + /// + /// Creates a new CodeDomReader, and takes in a list of assemblies in which to + /// look for elements. + /// + /// Assemblies in which to look for types that correspond + /// to elements. + public CodeDomReader(Assembly[] assemblies) + { + this.assemblies = assemblies; + } + + /// + /// Loads an XML file into a strongly-typed code dom. + /// + /// File to load into the code dom. + /// The strongly-typed object at the root of the tree. + public ISchemaElement Load(string filePath) + { + XmlDocument document = new XmlDocument(); + document.Load(filePath); + ISchemaElement schemaElement = null; + + foreach (XmlNode node in document.ChildNodes) + { + XmlElement element = node as XmlElement; + if (element != null) + { + if (schemaElement != null) + { + throw new InvalidOperationException("Multiple root elements found in file."); + } + + schemaElement = this.CreateObjectFromElement(element); + this.ParseObjectFromElement(schemaElement, element); + } + } + return schemaElement; + } + + /// + /// Parses an ISchemaElement from the XmlElement. + /// + /// ISchemaElement to fill in. + /// XmlElement to parse from. + private void ParseObjectFromElement(ISchemaElement schemaElement, XmlElement element) + { + foreach (XmlAttribute attribute in element.Attributes) + { + this.SetAttributeOnObject(schemaElement, attribute.LocalName, attribute.Value); + } + + foreach (XmlNode node in element.ChildNodes) + { + XmlElement childElement = node as XmlElement; + if (childElement != null) + { + ISchemaElement childSchemaElement = null; + ICreateChildren createChildren = schemaElement as ICreateChildren; + if (createChildren == null) + { + throw new InvalidOperationException("ISchemaElement with name " + element.LocalName + " does not implement ICreateChildren."); + } + else + { + childSchemaElement = createChildren.CreateChild(childElement.LocalName); + } + + if (childSchemaElement == null) + { + childSchemaElement = this.CreateObjectFromElement(childElement); + if (childSchemaElement == null) + { + throw new InvalidOperationException("XmlElement with name " + childElement.LocalName + " does not have a corresponding ISchemaElement."); + } + } + + this.ParseObjectFromElement(childSchemaElement, childElement); + IParentElement parentElement = (IParentElement)schemaElement; + parentElement.AddChild(childSchemaElement); + } + else + { + XmlText childText = node as XmlText; + if (childText != null) + { + this.SetAttributeOnObject(schemaElement, "Content", childText.Value); + } + } + } + } + + /// + /// Sets an attribute on an ISchemaElement. + /// + /// Schema element to set attribute on. + /// Name of the attribute to set. + /// Value to set on the attribute. + private void SetAttributeOnObject(ISchemaElement schemaElement, string name, string value) + { + ISetAttributes setAttributes = schemaElement as ISetAttributes; + if (setAttributes == null) + { + throw new InvalidOperationException("ISchemaElement with name " + + schemaElement.GetType().FullName.ToString() + + " does not implement ISetAttributes."); + } + else + { + setAttributes.SetAttribute(name, value); + } + } + + /// + /// Creates an object from an XML element by digging through the assembly list. + /// + /// XML Element to create an ISchemaElement from. + /// A constructed ISchemaElement. + private ISchemaElement CreateObjectFromElement(XmlElement element) + { + ISchemaElement schemaElement = null; + foreach (Assembly assembly in this.assemblies) + { + foreach (Type type in assembly.GetTypes()) + { + if (type.FullName.EndsWith(element.LocalName) + && typeof(ISchemaElement).IsAssignableFrom(type)) + { + schemaElement = (ISchemaElement)Activator.CreateInstance(type); + } + } + } + return schemaElement; + } + } +} diff --git a/src/WixBuildTools.XsdGen/ElementCollection.cs b/src/WixBuildTools.XsdGen/ElementCollection.cs new file mode 100644 index 00000000..e364dd11 --- /dev/null +++ b/src/WixBuildTools.XsdGen/ElementCollection.cs @@ -0,0 +1,642 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Serialize +{ + using System; + using System.Collections; + using System.Globalization; + + /// + /// Collection used in the CodeDOM for the children of a given element. Provides type-checking + /// on the allowed children to ensure that only allowed types are added. + /// + public class ElementCollection : ICollection, IEnumerable + { + private CollectionType collectionType; + private int minimum = 1; + private int maximum = 1; + private int totalContainedItems; + private int containersUsed; + private ArrayList items; + + /// + /// Creates a new element collection. + /// + /// Type of the collection to create. + public ElementCollection(CollectionType collectionType) + { + this.collectionType = collectionType; + this.items = new ArrayList(); + } + + /// + /// Creates a new element collection. + /// + /// Type of the collection to create. + /// When used with a type 'Choice', specifies a minimum number of allowed children. + /// When used with a type 'Choice', specifies a maximum number of allowed children. + public ElementCollection(CollectionType collectionType, int minimum, int maximum) : this(collectionType) + { + this.minimum = minimum; + this.maximum = maximum; + } + + /// + /// Enum representing types of XML collections. + /// + public enum CollectionType + { + /// + /// A choice type, corresponding to the XSD choice element. + /// + Choice, + + /// + /// A sequence type, corresponding to the XSD sequence element. + /// + Sequence + } + + /// + /// Gets the type of collection. + /// + /// The type of collection. + public CollectionType Type + { + get { return this.collectionType; } + } + + /// + /// Gets the count of child elements in this collection (counts ISchemaElements, not nested collections). + /// + /// The count of child elements in this collection (counts ISchemaElements, not nested collections). + public int Count + { + get { return this.totalContainedItems; } + } + + /// + /// Gets the flag specifying whether this collection is synchronized. Always returns false. + /// + /// The flag specifying whether this collection is synchronized. Always returns false. + public bool IsSynchronized + { + get { return false; } + } + + /// + /// Gets an object external callers can synchronize on. + /// + /// An object external callers can synchronize on. + public object SyncRoot + { + get { return this; } + } + + /// + /// Adds a child element to this collection. + /// + /// The element to add. + /// Thrown if the child is not of an allowed type. + public void AddElement(ISchemaElement element) + { + foreach (object obj in this.items) + { + bool containerUsed; + + CollectionItem collectionItem = obj as CollectionItem; + if (collectionItem != null) + { + containerUsed = collectionItem.Elements.Count != 0; + if (collectionItem.ElementType.IsAssignableFrom(element.GetType())) + { + collectionItem.AddElement(element); + + if (!containerUsed) + { + this.containersUsed++; + } + + this.totalContainedItems++; + return; + } + + continue; + } + + ElementCollection collection = obj as ElementCollection; + if (collection != null) + { + containerUsed = collection.Count != 0; + + try + { + collection.AddElement(element); + + if (!containerUsed) + { + this.containersUsed++; + } + + this.totalContainedItems++; + return; + } + catch (ArgumentException) + { + // Eat the exception and keep looking. We'll throw our own if we can't find its home. + } + + continue; + } + } + + throw new ArgumentException(String.Format( + CultureInfo.InvariantCulture, + "Element of type {0} is not valid for this collection.", + element.GetType().Name)); + } + + /// + /// Removes a child element from this collection. + /// + /// The element to remove. + /// Thrown if the element is not of an allowed type. + public void RemoveElement(ISchemaElement element) + { + foreach (object obj in this.items) + { + CollectionItem collectionItem = obj as CollectionItem; + if (collectionItem != null) + { + if (collectionItem.ElementType.IsAssignableFrom(element.GetType())) + { + if (collectionItem.Elements.Count == 0) + { + return; + } + + collectionItem.RemoveElement(element); + + if (collectionItem.Elements.Count == 0) + { + this.containersUsed--; + } + + this.totalContainedItems--; + return; + } + + continue; + } + + ElementCollection collection = obj as ElementCollection; + if (collection != null) + { + if (collection.Count == 0) + { + continue; + } + + try + { + collection.RemoveElement(element); + + if (collection.Count == 0) + { + this.containersUsed--; + } + + this.totalContainedItems--; + return; + } + catch (ArgumentException) + { + // Eat the exception and keep looking. We'll throw our own if we can't find its home. + } + + continue; + } + } + + throw new ArgumentException(String.Format( + CultureInfo.InvariantCulture, + "Element of type {0} is not valid for this collection.", + element.GetType().Name)); + } + + /// + /// Copies this collection to an array. + /// + /// Array to copy to. + /// Offset into the array. + public void CopyTo(Array array, int index) + { + int item = 0; + foreach (ISchemaElement element in this) + { + array.SetValue(element, (long)(item + index)); + item++; + } + } + + /// + /// Creates an enumerator for walking the elements in this collection. + /// + /// A newly created enumerator. + public IEnumerator GetEnumerator() + { + return new ElementCollectionEnumerator(this); + } + + /// + /// Gets an enumerable collection of children of a given type. + /// + /// Type of children to get. + /// A collection of children. + /// Thrown if the type isn't a valid child type. + public IEnumerable Filter(Type childType) + { + foreach (object container in this.items) + { + CollectionItem collectionItem = container as CollectionItem; + if (collectionItem != null) + { + if (collectionItem.ElementType.IsAssignableFrom(childType)) + { + return collectionItem.Elements; + } + + continue; + } + + ElementCollection elementCollection = container as ElementCollection; + if (elementCollection != null) + { + IEnumerable nestedFilter = elementCollection.Filter(childType); + if (nestedFilter != null) + { + return nestedFilter; + } + + continue; + } + } + + throw new ArgumentException(String.Format( + CultureInfo.InvariantCulture, + "Type {0} is not valid for this collection.", + childType.Name)); + } + + /// + /// Adds a type to this collection. + /// + /// CollectionItem representing the type to add. + public void AddItem(CollectionItem collectionItem) + { + this.items.Add(collectionItem); + } + + /// + /// Adds a nested collection to this collection. + /// + /// ElementCollection to add. + public void AddCollection(ElementCollection collection) + { + this.items.Add(collection); + } + + /// + /// Class used to represent a given type in the child collection of an element. Abstract, + /// has subclasses for choice and sequence (which can do cardinality checks). + /// + public abstract class CollectionItem + { + private Type elementType; + private ArrayList elements; + + /// + /// Creates a new CollectionItem for the given element type. + /// + /// Type of the element for this collection item. + public CollectionItem(Type elementType) + { + this.elementType = elementType; + this.elements = new ArrayList(); + } + + /// + /// Gets the type of this collection's items. + /// + public Type ElementType + { + get { return this.elementType; } + } + + /// + /// Gets the elements of this collection. + /// + public ArrayList Elements + { + get { return this.elements; } + } + + /// + /// Adds an element to this collection. Must be of an assignable type to the collection's + /// type. + /// + /// The element to add. + /// Thrown if the type isn't assignable to the collection's type. + public void AddElement(ISchemaElement element) + { + if (!this.elementType.IsAssignableFrom(element.GetType())) + { + throw new ArgumentException( + String.Format( + CultureInfo.InvariantCulture, + "Element must be a subclass of {0}, but was of type {1}.", + this.elementType.Name, + element.GetType().Name), + "element"); + } + + this.elements.Add(element); + } + + /// + /// Removes an element from this collection. + /// + /// The element to remove. + /// Thrown if the element's type isn't assignable to the collection's type. + public void RemoveElement(ISchemaElement element) + { + if (!this.elementType.IsAssignableFrom(element.GetType())) + { + throw new ArgumentException( + String.Format( + CultureInfo.InvariantCulture, + "Element must be a subclass of {0}, but was of type {1}.", + this.elementType.Name, + element.GetType().Name), + "element"); + } + + this.elements.Remove(element); + } + } + + /// + /// Class representing a choice item. Doesn't do cardinality checks. + /// + public class ChoiceItem : CollectionItem + { + /// + /// Creates a new choice item. + /// + /// Type of the created item. + public ChoiceItem(Type elementType) : base(elementType) + { + } + } + + /// + /// Class representing a sequence item. Can do cardinality checks, if required. + /// + public class SequenceItem : CollectionItem + { + private int minimum = 1; + private int maximum = 1; + + /// + /// Creates a new sequence item. + /// + /// Type of the created item. + public SequenceItem(Type elementType) : base(elementType) + { + } + + /// + /// Creates a new sequence item with the specified minimum and maximum. + /// + /// Type of the created item. + /// Minimum number of elements. + /// Maximum number of elements. + public SequenceItem(Type elementType, int minimum, int maximum) : base(elementType) + { + this.minimum = minimum; + this.maximum = maximum; + } + } + + /// + /// Enumerator for the ElementCollection. + /// + private class ElementCollectionEnumerator : IEnumerator + { + private ElementCollection collection; + private Stack collectionStack; + + /// + /// Creates a new ElementCollectionEnumerator. + /// + /// The collection to create an enumerator for. + public ElementCollectionEnumerator(ElementCollection collection) + { + this.collection = collection; + } + + /// + /// Gets the current object from the enumerator. + /// + public object Current + { + get + { + if (this.collectionStack != null && this.collectionStack.Count > 0) + { + CollectionTuple tuple = (CollectionTuple)this.collectionStack.Peek(); + object container = tuple.Collection.items[tuple.ContainerIndex]; + + CollectionItem collectionItem = container as CollectionItem; + if (collectionItem != null) + { + return collectionItem.Elements[tuple.ItemIndex]; + } + + throw new InvalidOperationException(String.Format( + CultureInfo.InvariantCulture, + "Element of type {0} found in enumerator. Must be ChoiceItem or SequenceItem.", + container.GetType().Name)); + } + + return null; + } + } + + /// + /// Resets the enumerator to the beginning. + /// + public void Reset() + { + if (this.collectionStack != null) + { + this.collectionStack.Clear(); + this.collectionStack = null; + } + } + + /// + /// Moves the enumerator to the next item. + /// + /// True if there is a next item, false otherwise. + public bool MoveNext() + { + if (this.collectionStack == null) + { + if (this.collection.Count == 0) + { + return false; + } + + this.collectionStack = new Stack(); + this.collectionStack.Push(new CollectionTuple(this.collection)); + } + + CollectionTuple tuple = (CollectionTuple)this.collectionStack.Peek(); + + if (this.FindNext(tuple)) + { + return true; + } + + this.collectionStack.Pop(); + if (this.collectionStack.Count == 0) + { + return false; + } + + return this.MoveNext(); + } + + /// + /// Pushes a collection onto the stack. + /// + /// The collection to push. + private void PushCollection(ElementCollection collection) + { + if (collection.Count <= 0) + { + throw new ArgumentException(String.Format( + CultureInfo.InvariantCulture, + "Collection has {0} elements. Must have at least one.", + collection.Count)); + } + + CollectionTuple tuple = new CollectionTuple(collection); + this.collectionStack.Push(tuple); + this.FindNext(tuple); + } + + /// + /// Finds the next item from a given tuple. + /// + /// The tuple to start looking from. + /// True if a next element is found, false otherwise. + private bool FindNext(CollectionTuple tuple) + { + object container = tuple.Collection.items[tuple.ContainerIndex]; + + CollectionItem collectionItem = container as CollectionItem; + if (collectionItem != null) + { + if (tuple.ItemIndex + 1 < collectionItem.Elements.Count) + { + tuple.ItemIndex++; + return true; + } + } + + ElementCollection elementCollection = container as ElementCollection; + if (elementCollection != null && elementCollection.Count > 0 && tuple.ItemIndex == -1) + { + tuple.ItemIndex++; + this.PushCollection(elementCollection); + return true; + } + + tuple.ItemIndex = 0; + + for (int i = tuple.ContainerIndex + 1; i < tuple.Collection.items.Count; ++i) + { + object nestedContainer = tuple.Collection.items[i]; + + CollectionItem nestedCollectionItem = nestedContainer as CollectionItem; + if (nestedCollectionItem != null) + { + if (nestedCollectionItem.Elements.Count > 0) + { + tuple.ContainerIndex = i; + return true; + } + } + + ElementCollection nestedElementCollection = nestedContainer as ElementCollection; + if (nestedElementCollection != null && nestedElementCollection.Count > 0) + { + tuple.ContainerIndex = i; + this.PushCollection(nestedElementCollection); + return true; + } + } + + return false; + } + + /// + /// Class representing a single point in the collection. Consists of an ElementCollection, + /// a container index, and an index into the container. + /// + private class CollectionTuple + { + private ElementCollection collection; + private int containerIndex; + private int itemIndex = -1; + + /// + /// Creates a new CollectionTuple. + /// + /// The collection for the tuple. + public CollectionTuple(ElementCollection collection) + { + this.collection = collection; + } + + /// + /// Gets the collection for the tuple. + /// + public ElementCollection Collection + { + get { return this.collection; } + } + + /// + /// Gets and sets the index of the container in the collection. + /// + public int ContainerIndex + { + get { return this.containerIndex; } + set { this.containerIndex = value; } + } + + /// + /// Gets and sets the index of the item in the container. + /// + public int ItemIndex + { + get { return this.itemIndex; } + set { this.itemIndex = value; } + } + } + } + } +} diff --git a/src/WixBuildTools.XsdGen/StronglyTypedClasses.cs b/src/WixBuildTools.XsdGen/StronglyTypedClasses.cs new file mode 100644 index 00000000..4a41f8a9 --- /dev/null +++ b/src/WixBuildTools.XsdGen/StronglyTypedClasses.cs @@ -0,0 +1,1498 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Tools +{ + using System; + using System.CodeDom; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.IO; + using System.Reflection; + using System.Text; + using System.Text.RegularExpressions; + using System.Xml; + using System.Xml.Schema; + + /// + /// Type containing static Generate method, which fills in a compile unit from a + /// given schema. + /// + internal class StronglyTypedClasses + { + private static string outputXmlComment = "Processes this element and all child elements into an XmlWriter."; + private static Hashtable simpleTypeNamesToClrTypeNames; + private static Dictionary typeNamesToEnumDeclarations; + private static Dictionary enumsToParseMethodClasses; + private static Regex multiUppercaseNameRegex = new Regex("[A-Z][A-Z][A-Z]", RegexOptions.Compiled); + private static Dictionary refToAttributeGroups; + private static CodeTypeDeclaration enumHelperClass; + + /// + /// Private constructor for static class. + /// + private StronglyTypedClasses() + { + } + + /// + /// Generates strongly typed serialization classes for the given schema document + /// under the given namespace and generates a code compile unit. + /// + /// Schema document to generate classes for. + /// Namespace to be used for the generated code. + /// Namespace in which to find common classes and interfaces, + /// like ISchemaElement. + /// A fully populated CodeCompileUnit, which can be serialized in the language of choice. + public static CodeCompileUnit Generate(XmlSchema xmlSchema, string generateNamespace, string commonNamespace) + { + if (xmlSchema == null) + { + throw new ArgumentNullException("xmlSchema"); + } + if (generateNamespace == null) + { + throw new ArgumentNullException("generateNamespace"); + } + + simpleTypeNamesToClrTypeNames = new Hashtable(); + typeNamesToEnumDeclarations = new Dictionary(); + refToAttributeGroups = new Dictionary(); + enumsToParseMethodClasses = new Dictionary(); + + CodeCompileUnit codeCompileUnit = new CodeCompileUnit(); + CodeNamespace codeNamespace = new CodeNamespace(generateNamespace); + codeCompileUnit.Namespaces.Add(codeNamespace); + codeNamespace.Imports.Add(new CodeNamespaceImport("System")); + codeNamespace.Imports.Add(new CodeNamespaceImport("System.CodeDom.Compiler")); // for GeneratedCodeAttribute + codeNamespace.Imports.Add(new CodeNamespaceImport("System.Collections")); + codeNamespace.Imports.Add(new CodeNamespaceImport("System.Diagnostics.CodeAnalysis")); + codeNamespace.Imports.Add(new CodeNamespaceImport("System.Globalization")); + codeNamespace.Imports.Add(new CodeNamespaceImport("System.Xml")); + if (commonNamespace != null) + { + codeNamespace.Imports.Add(new CodeNamespaceImport(commonNamespace)); + } + + // NOTE: This hash table serves double duty so be sure to have the XSD + // type name mapped to the CLR type name *and* the CLR type name + // mapped to the same CLR type name. Look at long and bool for + // examples below (and before you ask, no I don't know why DateTime + // just works). + simpleTypeNamesToClrTypeNames.Add("dateTime", "DateTime"); + simpleTypeNamesToClrTypeNames.Add("integer", "int"); + simpleTypeNamesToClrTypeNames.Add("int", "int"); + simpleTypeNamesToClrTypeNames.Add("NMTOKEN", "string"); + simpleTypeNamesToClrTypeNames.Add("string", "string"); + simpleTypeNamesToClrTypeNames.Add("nonNegativeInteger", "long"); + simpleTypeNamesToClrTypeNames.Add("long", "long"); + simpleTypeNamesToClrTypeNames.Add("boolean", "bool"); + simpleTypeNamesToClrTypeNames.Add("bool", "bool"); + + xmlSchema.Compile(null); + + foreach (XmlSchemaAttributeGroup schemaAttributeGroup in xmlSchema.AttributeGroups.Values) + { + refToAttributeGroups.Add(schemaAttributeGroup.Name, schemaAttributeGroup); + } + + foreach (XmlSchemaObject schemaObject in xmlSchema.SchemaTypes.Values) + { + XmlSchemaSimpleType schemaSimpleType = schemaObject as XmlSchemaSimpleType; + if (schemaSimpleType != null) + { + ProcessSimpleType(schemaSimpleType, codeNamespace); + } + } + + foreach (XmlSchemaObject schemaObject in xmlSchema.SchemaTypes.Values) + { + XmlSchemaComplexType schemaComplexType = schemaObject as XmlSchemaComplexType; + if (schemaComplexType != null) + { + ProcessComplexType(schemaComplexType, codeNamespace); + } + } + + foreach (XmlSchemaObject schemaObject in xmlSchema.Elements.Values) + { + XmlSchemaElement schemaElement = schemaObject as XmlSchemaElement; + if (schemaElement != null) + { + ProcessElement(schemaElement, codeNamespace); + } + } + + return codeCompileUnit; + } + + /// + /// Processes an XmlSchemaElement into corresponding types. + /// + /// XmlSchemaElement to be processed. + /// CodeNamespace to be used when outputting code. + private static void ProcessElement(XmlSchemaElement schemaElement, CodeNamespace codeNamespace) + { + string elementType = schemaElement.SchemaTypeName.Name; + string elementNamespace = schemaElement.QualifiedName.Namespace; + string elementDocumentation = GetDocumentation(schemaElement.Annotation); + + if ((elementType == null || elementType.Length == 0) && schemaElement.SchemaType != null) + { + ProcessComplexType(schemaElement.Name, elementNamespace, (XmlSchemaComplexType)schemaElement.SchemaType, elementDocumentation, codeNamespace); + } + else + { + if (elementType == null || elementType.Length == 0) + { + elementType = "string"; + } + + CodeTypeDeclaration typeDeclaration = new CodeTypeDeclaration(schemaElement.Name); + typeDeclaration.CustomAttributes.Add(GetGeneratedCodeAttribute()); + typeDeclaration.Attributes = MemberAttributes.Public; + typeDeclaration.IsClass = true; + + if (elementDocumentation != null) + { + GenerateSummaryComment(typeDeclaration.Comments, elementDocumentation); + } + + CodeMemberMethod outputXmlMethod = new CodeMemberMethod(); + outputXmlMethod.Attributes = MemberAttributes.Public; + outputXmlMethod.ImplementationTypes.Add("ISchemaElement"); + outputXmlMethod.Name = "OutputXml"; + outputXmlMethod.Parameters.Add(new CodeParameterDeclarationExpression("XmlWriter", "writer")); + outputXmlMethod.Statements.Add(GetArgumentNullCheckStatement("writer", false)); + outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteStartElement", new CodeSnippetExpression(String.Concat("\"", schemaElement.Name, "\"")), new CodeSnippetExpression(String.Concat("\"", elementNamespace, "\"")))); + GenerateSummaryComment(outputXmlMethod.Comments, outputXmlComment); + + if (simpleTypeNamesToClrTypeNames.ContainsKey(elementType)) + { + CodeMemberField parentField = new CodeMemberField("ISchemaElement", "parentElement"); + typeDeclaration.Members.Add(parentField); + + CodeMemberProperty parentProperty = new CodeMemberProperty(); + parentProperty.Attributes = MemberAttributes.Public; + parentProperty.ImplementationTypes.Add("ISchemaElement"); + parentProperty.Name = "ParentElement"; + parentProperty.Type = new CodeTypeReference("ISchemaElement"); + parentProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"))); + parentProperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"), new CodeVariableReferenceExpression("value"))); + typeDeclaration.Members.Add(parentProperty); + + CodeMemberMethod setAttributeMethod = new CodeMemberMethod(); + setAttributeMethod.Attributes = MemberAttributes.Public; + setAttributeMethod.ImplementationTypes.Add("ISetAttributes"); + setAttributeMethod.Name = "SetAttribute"; + setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "name")); + setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "value")); + setAttributeMethod.PrivateImplementationType = new CodeTypeReference("ISetAttributes"); + setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")); + setAttributeMethod.Statements.Add(GetArgumentNullCheckStatement("name", true)); + + GenerateFieldAndProperty("Content", (string)simpleTypeNamesToClrTypeNames[elementType], typeDeclaration, outputXmlMethod, setAttributeMethod, null, elementDocumentation, true, false); + + typeDeclaration.Members.Add(setAttributeMethod); + typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISetAttributes")); + } + else + { + typeDeclaration.BaseTypes.Add(elementType); + outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeBaseReferenceExpression(), "OutputXml", new CodeVariableReferenceExpression("writer"))); + outputXmlMethod.Attributes |= MemberAttributes.Override; + } + + outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteEndElement")); + + typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISchemaElement")); + typeDeclaration.Members.Add(outputXmlMethod); + codeNamespace.Types.Add(typeDeclaration); + } + } + + /// + /// Processes an XmlSchemaComplexType into corresponding types. + /// + /// XmlSchemaComplexType to be processed. + /// CodeNamespace to be used when outputting code. + private static void ProcessComplexType(XmlSchemaComplexType complexType, CodeNamespace codeNamespace) + { + CodeMemberMethod outputXmlMethod = new CodeMemberMethod(); + outputXmlMethod.Attributes = MemberAttributes.Public; + outputXmlMethod.ImplementationTypes.Add("ISchemaElement"); + outputXmlMethod.Name = "OutputXml"; + outputXmlMethod.Parameters.Add(new CodeParameterDeclarationExpression("XmlWriter", "writer")); + outputXmlMethod.Statements.Add(GetArgumentNullCheckStatement("writer", false)); + GenerateSummaryComment(outputXmlMethod.Comments, outputXmlComment); + + CodeMemberMethod setAttributeMethod = new CodeMemberMethod(); + setAttributeMethod.Attributes = MemberAttributes.Public; + setAttributeMethod.ImplementationTypes.Add("ISetAttributes"); + setAttributeMethod.Name = "SetAttribute"; + setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "name")); + setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "value")); + setAttributeMethod.PrivateImplementationType = new CodeTypeReference("ISetAttributes"); + setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")); + setAttributeMethod.Statements.Add(GetArgumentNullCheckStatement("name", true)); + + string documentation = GetDocumentation(complexType.Annotation); + + ProcessSimpleContent(complexType.Name, (XmlSchemaSimpleContentExtension)complexType.ContentModel.Content, documentation, codeNamespace, outputXmlMethod, setAttributeMethod, true); + } + + /// + /// Processes an XmlSchemaComplexType into corresponding types. + /// + /// Name to use for the type being output. + /// Namespace of the xml element. + /// XmlSchemaComplexType to be processed. + /// Documentation for the element. + /// CodeNamespace to be used when outputting code. + private static void ProcessComplexType(string typeName, string elementNamespace, XmlSchemaComplexType complexType, string documentation, CodeNamespace codeNamespace) + { + CodeMemberMethod outputXmlMethod = new CodeMemberMethod(); + outputXmlMethod.Attributes = MemberAttributes.Public; + outputXmlMethod.ImplementationTypes.Add("ISchemaElement"); + outputXmlMethod.Name = "OutputXml"; + outputXmlMethod.Parameters.Add(new CodeParameterDeclarationExpression("XmlWriter", "writer")); + outputXmlMethod.Statements.Add(GetArgumentNullCheckStatement("writer", false)); + outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteStartElement", new CodeSnippetExpression(String.Concat("\"", typeName, "\"")), new CodeSnippetExpression(String.Concat("\"", elementNamespace, "\"")))); + GenerateSummaryComment(outputXmlMethod.Comments, outputXmlComment); + + CodeMemberMethod setAttributeMethod = new CodeMemberMethod(); + setAttributeMethod.Attributes = MemberAttributes.Public; + setAttributeMethod.ImplementationTypes.Add("ISetAttributes"); + setAttributeMethod.Name = "SetAttribute"; + setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "name")); + setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "value")); + setAttributeMethod.PrivateImplementationType = new CodeTypeReference("ISetAttributes"); + setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")); + setAttributeMethod.Statements.Add(GetArgumentNullCheckStatement("name", true)); + + if (complexType.ContentModel == null) + { + CodeTypeDeclaration typeDeclaration = new CodeTypeDeclaration(typeName); + typeDeclaration.CustomAttributes.Add(GetGeneratedCodeAttribute()); + typeDeclaration.Attributes = MemberAttributes.Public; + typeDeclaration.IsClass = true; + CodeIterationStatement childEnumStatement = null; + + if (documentation != null) + { + GenerateSummaryComment(typeDeclaration.Comments, documentation); + } + + if (complexType.Particle != null) + { + CodeMemberField childrenField = new CodeMemberField("ElementCollection", "children"); + typeDeclaration.Members.Add(childrenField); + + CodeMemberProperty childrenProperty = new CodeMemberProperty(); + childrenProperty.Attributes = MemberAttributes.Public; + childrenProperty.ImplementationTypes.Add("IParentElement"); + childrenProperty.Name = "Children"; + childrenProperty.Type = new CodeTypeReference("IEnumerable"); + childrenProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"))); + typeDeclaration.Members.Add(childrenProperty); + + CodeMemberProperty filterChildrenProperty = new CodeMemberProperty(); + filterChildrenProperty.Attributes = MemberAttributes.Public; + filterChildrenProperty.ImplementationTypes.Add("IParentElement"); + filterChildrenProperty.Name = "Item"; + filterChildrenProperty.Parameters.Add(new CodeParameterDeclarationExpression(typeof(Type), "childType")); + filterChildrenProperty.Type = new CodeTypeReference("IEnumerable"); + filterChildrenProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), "Filter", new CodeVariableReferenceExpression("childType")))); + filterChildrenProperty.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1043:UseIntegralOrStringArgumentForIndexers")); + typeDeclaration.Members.Add(filterChildrenProperty); + + CodeMemberMethod addChildMethod = new CodeMemberMethod(); + addChildMethod.Attributes = MemberAttributes.Public; + addChildMethod.ImplementationTypes.Add("IParentElement"); + addChildMethod.Name = "AddChild"; + addChildMethod.Parameters.Add(new CodeParameterDeclarationExpression("ISchemaElement", "child")); + addChildMethod.Statements.Add(GetArgumentNullCheckStatement("child", false)); + CodeExpressionStatement addChildStatement = new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), "AddElement", new CodeVariableReferenceExpression("child"))); + addChildMethod.Statements.Add(addChildStatement); + CodeAssignStatement setParentStatement = new CodeAssignStatement(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("child"), "ParentElement"), new CodeThisReferenceExpression()); + addChildMethod.Statements.Add(setParentStatement); + typeDeclaration.Members.Add(addChildMethod); + + CodeMemberMethod removeChildMethod = new CodeMemberMethod(); + removeChildMethod.Attributes = MemberAttributes.Public; + removeChildMethod.ImplementationTypes.Add("IParentElement"); + removeChildMethod.Name = "RemoveChild"; + removeChildMethod.Parameters.Add(new CodeParameterDeclarationExpression("ISchemaElement", "child")); + removeChildMethod.Statements.Add(GetArgumentNullCheckStatement("child", false)); + CodeExpressionStatement removeChildStatement = new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), "RemoveElement", new CodeVariableReferenceExpression("child"))); + removeChildMethod.Statements.Add(removeChildStatement); + CodeAssignStatement nullParentStatement = new CodeAssignStatement(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("child"), "ParentElement"), new CodePrimitiveExpression(null)); + removeChildMethod.Statements.Add(nullParentStatement); + typeDeclaration.Members.Add(removeChildMethod); + + CodeMemberMethod createChildMethod = new CodeMemberMethod(); + createChildMethod.Attributes = MemberAttributes.Public; + createChildMethod.ImplementationTypes.Add("ICreateChildren"); + createChildMethod.Name = "CreateChild"; + createChildMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "childName")); + createChildMethod.PrivateImplementationType = new CodeTypeReference("ICreateChildren"); + createChildMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")); + createChildMethod.ReturnType = new CodeTypeReference("ISchemaElement"); + createChildMethod.Statements.Add(GetArgumentNullCheckStatement("childName", true)); + createChildMethod.Statements.Add(new CodeVariableDeclarationStatement("ISchemaElement", "childValue", new CodePrimitiveExpression(null))); + + CodeConstructor typeConstructor = new CodeConstructor(); + typeConstructor.Attributes = MemberAttributes.Public; + + CodeVariableReferenceExpression collectionVariable = null; + + XmlSchemaChoice schemaChoice = complexType.Particle as XmlSchemaChoice; + if (schemaChoice != null) + { + collectionVariable = ProcessSchemaGroup(schemaChoice, typeConstructor, createChildMethod); + } + else + { + XmlSchemaSequence schemaSequence = complexType.Particle as XmlSchemaSequence; + if (schemaSequence != null) + { + collectionVariable = ProcessSchemaGroup(schemaSequence, typeConstructor, createChildMethod); + } + } + + typeConstructor.Statements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), collectionVariable)); + typeDeclaration.Members.Add(typeConstructor); + + CodeConditionStatement childNameNotFound = new CodeConditionStatement(); + childNameNotFound.Condition = new CodeBinaryOperatorExpression(new CodePrimitiveExpression(null), CodeBinaryOperatorType.ValueEquality, new CodeVariableReferenceExpression("childValue")); + childNameNotFound.TrueStatements.Add(new CodeThrowExceptionStatement(new CodeObjectCreateExpression("InvalidOperationException", new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("String"), "Concat", new CodeVariableReferenceExpression("childName"), new CodeSnippetExpression("\" is not a valid child name.\""))))); + createChildMethod.Statements.Add(childNameNotFound); + + if (createChildMethod.Statements.Count > 8) + { + createChildMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")); + } + + createChildMethod.Statements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression("childValue"))); + typeDeclaration.Members.Add(createChildMethod); + + childEnumStatement = new CodeIterationStatement(); + childEnumStatement.InitStatement = new CodeVariableDeclarationStatement("IEnumerator", "enumerator", new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), "GetEnumerator")); + childEnumStatement.TestExpression = new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("enumerator"), "MoveNext"); + childEnumStatement.Statements.Add(new CodeVariableDeclarationStatement("ISchemaElement", "childElement", new CodeCastExpression("ISchemaElement", new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("enumerator"), "Current")))); + childEnumStatement.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("childElement"), "OutputXml", new CodeVariableReferenceExpression("writer"))); + childEnumStatement.IncrementStatement = new CodeExpressionStatement(new CodeSnippetExpression("")); + + typeDeclaration.BaseTypes.Add(new CodeTypeReference("IParentElement")); + typeDeclaration.BaseTypes.Add(new CodeTypeReference("ICreateChildren")); + } + + // TODO: Handle xs:anyAttribute. + ProcessAttributes(complexType.Attributes, typeDeclaration, outputXmlMethod, setAttributeMethod); + + if (childEnumStatement != null) + { + outputXmlMethod.Statements.Add(childEnumStatement); + } + + typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISchemaElement")); + typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISetAttributes")); + + CodeMemberField parentField = new CodeMemberField("ISchemaElement", "parentElement"); + typeDeclaration.Members.Add(parentField); + + CodeMemberProperty parentProperty = new CodeMemberProperty(); + parentProperty.Attributes = MemberAttributes.Public; + parentProperty.ImplementationTypes.Add("ISchemaElement"); + parentProperty.Name = "ParentElement"; + parentProperty.Type = new CodeTypeReference("ISchemaElement"); + parentProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"))); + parentProperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"), new CodeVariableReferenceExpression("value"))); + typeDeclaration.Members.Add(parentProperty); + + if (outputXmlMethod.Statements.Count > 8) + { + outputXmlMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")); + } + + if (setAttributeMethod.Statements.Count > 8) + { + setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")); + } + + typeDeclaration.Members.Add(outputXmlMethod); + typeDeclaration.Members.Add(setAttributeMethod); + codeNamespace.Types.Add(typeDeclaration); + } + else + { + ProcessSimpleContent(typeName, (XmlSchemaSimpleContentExtension)complexType.ContentModel.Content, documentation, codeNamespace, outputXmlMethod, setAttributeMethod, false); + } + + outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteEndElement")); + } + + /// + /// Processes a collection of attributes, generating the required fields and properties. + /// + /// List of attribute or attributeGroupRef elements being processed. + /// CodeTypeDeclaration to be used when outputting code. + /// Member method for the OutputXml method. + /// Member method for the SetAttribute method. + private static void ProcessAttributes(XmlSchemaObjectCollection attributes, CodeTypeDeclaration typeDeclaration, CodeMemberMethod outputXmlMethod, CodeMemberMethod setAttributeMethod) + { + foreach (XmlSchemaObject schemaObject in attributes) + { + XmlSchemaAttribute schemaAttribute = schemaObject as XmlSchemaAttribute; + if (schemaAttribute != null) + { + ProcessAttribute(schemaAttribute, typeDeclaration, outputXmlMethod, setAttributeMethod); + } + else + { + XmlSchemaAttributeGroupRef schemaAttributeGroupRef = schemaObject as XmlSchemaAttributeGroupRef; + if (schemaAttributeGroupRef != null) + { + XmlSchemaAttributeGroup schemaAttributeGroup = refToAttributeGroups[schemaAttributeGroupRef.RefName.Name]; + // recurse! + ProcessAttributes(schemaAttributeGroup.Attributes, typeDeclaration, outputXmlMethod, setAttributeMethod); + } + } + } + } + + /// + /// Processes an XmlSchemaGroupBase element. + /// + /// Element group to process. + /// Constructor to which statements should be added. + /// Method used for creating children on read-in. + /// A reference to the local variable containing the collection. + private static CodeVariableReferenceExpression ProcessSchemaGroup(XmlSchemaGroupBase schemaGroup, CodeConstructor constructor, CodeMemberMethod createChildMethod) + { + return ProcessSchemaGroup(schemaGroup, constructor, createChildMethod, 0); + } + + /// + /// Processes an XmlSchemaGroupBase element. + /// + /// Element group to process. + /// Constructor to which statements should be added. + /// Method used for creating children on read-in. + /// Depth to which this collection is nested. + /// A reference to the local variable containing the collection. + private static CodeVariableReferenceExpression ProcessSchemaGroup(XmlSchemaGroupBase schemaGroup, CodeConstructor constructor, CodeMemberMethod createChildMethod, int depth) + { + string collectionName = String.Format("childCollection{0}", depth); + CodeVariableReferenceExpression collectionVariableReference = new CodeVariableReferenceExpression(collectionName); + CodeVariableDeclarationStatement collectionStatement = new CodeVariableDeclarationStatement("ElementCollection", collectionName); + if (schemaGroup is XmlSchemaChoice) + { + collectionStatement.InitExpression = new CodeObjectCreateExpression("ElementCollection", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("ElementCollection.CollectionType"), "Choice")); + } + else + { + collectionStatement.InitExpression = new CodeObjectCreateExpression("ElementCollection", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("ElementCollection.CollectionType"), "Sequence")); + } + constructor.Statements.Add(collectionStatement); + + foreach (XmlSchemaObject obj in schemaGroup.Items) + { + XmlSchemaElement schemaElement = obj as XmlSchemaElement; + if (schemaElement != null) + { + if (schemaGroup is XmlSchemaChoice) + { + CodeMethodInvokeExpression addItemInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddItem", new CodeObjectCreateExpression("ElementCollection.ChoiceItem", new CodeTypeOfExpression(schemaElement.RefName.Name))); + constructor.Statements.Add(addItemInvoke); + } + else + { + CodeMethodInvokeExpression addItemInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddItem", new CodeObjectCreateExpression("ElementCollection.SequenceItem", new CodeTypeOfExpression(schemaElement.RefName.Name))); + constructor.Statements.Add(addItemInvoke); + } + + CodeConditionStatement createChildIf = new CodeConditionStatement(); + createChildIf.Condition = new CodeBinaryOperatorExpression(new CodeSnippetExpression(String.Concat("\"", schemaElement.RefName.Name, "\"")), CodeBinaryOperatorType.ValueEquality, new CodeVariableReferenceExpression("childName")); + createChildIf.TrueStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression("childValue"), new CodeObjectCreateExpression(schemaElement.RefName.Name))); + createChildMethod.Statements.Add(createChildIf); + + continue; + } + + XmlSchemaAny schemaAny = obj as XmlSchemaAny; + if (schemaAny != null) + { + if (schemaGroup is XmlSchemaChoice) + { + CodeMethodInvokeExpression addItemInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddItem", new CodeObjectCreateExpression("ElementCollection.ChoiceItem", new CodeTypeOfExpression("ISchemaElement"))); + constructor.Statements.Add(addItemInvoke); + } + else + { + CodeMethodInvokeExpression addItemInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddItem", new CodeObjectCreateExpression("ElementCollection.SequenceItem", new CodeTypeOfExpression("ISchemaElement"), new CodeSnippetExpression("0"), new CodeSnippetExpression("-1"))); + constructor.Statements.Add(addItemInvoke); + } + + continue; + } + + XmlSchemaGroupBase schemaGroupBase = obj as XmlSchemaGroupBase; + if (schemaGroupBase != null) + { + CodeVariableReferenceExpression nestedCollectionReference = ProcessSchemaGroup(schemaGroupBase, constructor, createChildMethod, depth + 1); + CodeMethodInvokeExpression addCollectionInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddCollection", nestedCollectionReference); + constructor.Statements.Add(addCollectionInvoke); + + continue; + } + } + + return collectionVariableReference; + } + + /// + /// Processes an XmlSchemaSimpleContentExtension into corresponding types. + /// + /// Name of the type being generated. + /// XmlSchemaSimpleContentExtension being processed. + /// Documentation for the simple content. + /// CodeNamespace to be used when outputting code. + /// Method to use when outputting Xml. + /// Method to use when setting an attribute. + /// If true, generate an abstract class. + private static void ProcessSimpleContent(string typeName, XmlSchemaSimpleContentExtension simpleContent, string documentation, CodeNamespace codeNamespace, CodeMemberMethod outputXmlMethod, CodeMemberMethod setAttributeMethod, bool abstractClass) + { + CodeTypeDeclaration typeDeclaration = new CodeTypeDeclaration(typeName); + typeDeclaration.CustomAttributes.Add(GetGeneratedCodeAttribute()); + typeDeclaration.Attributes = MemberAttributes.Public; + typeDeclaration.IsClass = true; + + if (documentation != null) + { + GenerateSummaryComment(typeDeclaration.Comments, documentation); + } + + if (abstractClass) + { + typeDeclaration.TypeAttributes = System.Reflection.TypeAttributes.Abstract | System.Reflection.TypeAttributes.Public; + } + + // TODO: Handle xs:anyAttribute here. + foreach (XmlSchemaAttribute schemaAttribute in simpleContent.Attributes) + { + ProcessAttribute(schemaAttribute, typeDeclaration, outputXmlMethod, setAttributeMethod); + } + + // This needs to come last, so that the generation code generates the inner content after the attributes. + string contentDocumentation = GetDocumentation(simpleContent.Annotation); + GenerateFieldAndProperty("Content", (string)simpleTypeNamesToClrTypeNames[simpleContent.BaseTypeName.Name], typeDeclaration, outputXmlMethod, setAttributeMethod, null, contentDocumentation, true, false); + + typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISchemaElement")); + typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISetAttributes")); + + CodeMemberField parentField = new CodeMemberField("ISchemaElement", "parentElement"); + typeDeclaration.Members.Add(parentField); + + CodeMemberProperty parentProperty = new CodeMemberProperty(); + parentProperty.Attributes = MemberAttributes.Public; + parentProperty.ImplementationTypes.Add("ISchemaElement"); + parentProperty.Name = "ParentElement"; + parentProperty.Type = new CodeTypeReference("ISchemaElement"); + parentProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"))); + parentProperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"), new CodeVariableReferenceExpression("value"))); + typeDeclaration.Members.Add(parentProperty); + + if (outputXmlMethod.Statements.Count > 8) + { + outputXmlMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")); + } + + if (setAttributeMethod.Statements.Count > 8) + { + setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")); + } + + typeDeclaration.Members.Add(outputXmlMethod); + typeDeclaration.Members.Add(setAttributeMethod); + codeNamespace.Types.Add(typeDeclaration); + } + + /// + /// Processes an attribute, generating the required field and property. Potentially generates + /// an enum for an attribute restriction. + /// + /// Attribute element being processed. + /// CodeTypeDeclaration to be used when outputting code. + /// Member method for the OutputXml method. + /// Member method for the SetAttribute method. + private static void ProcessAttribute(XmlSchemaAttribute attribute, CodeTypeDeclaration typeDeclaration, CodeMemberMethod outputXmlMethod, CodeMemberMethod setAttributeMethod) + { + string attributeName = attribute.QualifiedName.Name; + string rawAttributeType = attribute.AttributeSchemaType.QualifiedName.Name; + string attributeType = null; + EnumDeclaration enumDeclaration = null; + if (rawAttributeType == null || rawAttributeType.Length == 0) + { + ProcessSimpleType(attributeName, attribute.AttributeSchemaType, true, out enumDeclaration, out attributeType); + + if (enumDeclaration != null) + { + typeDeclaration.Members.Add(enumDeclaration.TypeDeclaration); + AddEnumHelperMethods(enumDeclaration, typeDeclaration); + } + } + else + { + attributeType = (string)simpleTypeNamesToClrTypeNames[rawAttributeType]; + } + + string documentation = GetDocumentation(attribute.Annotation); + + // TODO: Handle required fields. + GenerateFieldAndProperty(attributeName, attributeType, typeDeclaration, outputXmlMethod, setAttributeMethod, enumDeclaration, documentation, false, false); + } + + /// + /// Gets the first sentence of a documentation element and returns it as a string. + /// + /// The annotation in which to look for a documentation element. + /// The string representing the first sentence, or null if none found. + private static string GetDocumentation(XmlSchemaAnnotation annotation) + { + string documentation = null; + + if (annotation != null && annotation.Items != null) + { + foreach (XmlSchemaObject obj in annotation.Items) + { + XmlSchemaDocumentation schemaDocumentation = obj as XmlSchemaDocumentation; + if (schemaDocumentation != null) + { + if (schemaDocumentation.Markup.Length > 0) + { + XmlText text = schemaDocumentation.Markup[0] as XmlText; + if (text != null) + { + documentation = text.Value; + } + } + break; + } + } + } + + if (documentation != null) + { + documentation = documentation.Trim(); + } + return documentation; + } + + /// + /// Makes a valid enum value out of the passed in value. May remove spaces, add 'Item' to the + /// start if it begins with an integer, or strip out punctuation. + /// + /// Enum value to be processed. + /// Enum value with invalid characters removed. + private static string MakeEnumValue(string enumValue) + { + if (Char.IsDigit(enumValue[0])) + { + enumValue = String.Concat("Item", enumValue); + } + + StringBuilder newValue = new StringBuilder(); + for (int i = 0; i < enumValue.Length; ++i) + { + if (!Char.IsPunctuation(enumValue[i]) && !Char.IsSymbol(enumValue[i]) && !Char.IsWhiteSpace(enumValue[i])) + { + newValue.Append(enumValue[i]); + } + } + + return newValue.ToString(); + } + + /// + /// Generates the private field and public property for a piece of data. + /// + /// Name of the property being generated. + /// Name of the type for the property. + /// Type declaration into which the field and property should be placed. + /// Member method for the OutputXml method. + /// Member method for the SetAttribute method. + /// EnumDeclaration, which is null unless called from a locally defined enum attribute. + /// Comment string to be placed on the property. + /// If true, the field will be placed in nested content when outputting to XML. + /// If true, the generated serialization code will throw if the field is not set. + private static void GenerateFieldAndProperty(string propertyName, string typeName, CodeTypeDeclaration typeDeclaration, CodeMemberMethod outputXmlMethod, CodeMemberMethod setAttributeMethod, EnumDeclaration enumDeclaration, string documentation, bool nestedContent, bool requiredField) + { + string fieldName = String.Concat(propertyName.Substring(0, 1).ToLower(), propertyName.Substring(1), "Field"); + string fieldNameSet = String.Concat(fieldName, "Set"); + Type type = GetClrTypeByXmlName(typeName); + CodeMemberField fieldMember; + if (type == null) + { + fieldMember = new CodeMemberField(typeName, fieldName); + } + else + { + fieldMember = new CodeMemberField(type, fieldName); + } + fieldMember.Attributes = MemberAttributes.Private; + typeDeclaration.Members.Add(fieldMember); + typeDeclaration.Members.Add(new CodeMemberField(typeof(bool), fieldNameSet)); + + CodeMemberProperty propertyMember = new CodeMemberProperty(); + propertyMember.Attributes = MemberAttributes.Public | MemberAttributes.Final; + if (documentation != null) + { + GenerateSummaryComment(propertyMember.Comments, documentation); + } + propertyMember.Name = propertyName; + if (type == null) + { + propertyMember.Type = new CodeTypeReference(typeName); + } + else + { + propertyMember.Type = new CodeTypeReference(type); + } + + if (propertyMember.Name.StartsWith("src")) + { + propertyMember.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly")); + } + else if (StronglyTypedClasses.multiUppercaseNameRegex.Match(propertyMember.Name).Success) + { + propertyMember.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Naming", "CA1705:LongAcronymsShouldBePascalCased")); + } + + CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement(); + returnStatement.Expression = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName); + propertyMember.GetStatements.Add(returnStatement); + + CodeAssignStatement assignmentStatement = new CodeAssignStatement(); + propertyMember.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldNameSet), new CodePrimitiveExpression(true))); + assignmentStatement.Left = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName); + assignmentStatement.Right = new CodePropertySetValueReferenceExpression(); + propertyMember.SetStatements.Add(assignmentStatement); + + CodeConditionStatement fieldSetStatement = new CodeConditionStatement(); + fieldSetStatement.Condition = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldNameSet); + + CodeAssignStatement fieldSetAttrStatement = new CodeAssignStatement(); + fieldSetAttrStatement.Left = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldNameSet); + fieldSetAttrStatement.Right = new CodePrimitiveExpression(true); + + CodeConditionStatement attributeNameMatchStatement = new CodeConditionStatement(); + attributeNameMatchStatement.Condition = new CodeBinaryOperatorExpression(new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), CodeBinaryOperatorType.IdentityEquality, new CodeVariableReferenceExpression("name")); + + string clrTypeName = (string)simpleTypeNamesToClrTypeNames[typeName]; + switch (clrTypeName) + { + case "string": + if (nestedContent) + { + fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteString", new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName))); + attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeVariableReferenceExpression("value"))); + } + else + { + fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName))); + attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeVariableReferenceExpression("value"))); + } + break; + case "bool": + if (nestedContent) + { + fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteString", new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); + } + else + { + fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); + } + attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("Convert"), "ToBoolean", new CodeVariableReferenceExpression("value"), new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); + break; + case "int": + case "long": + if (nestedContent) + { + fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteString", new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); + } + else + { + fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); + } + attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("Convert"), "ToInt32", new CodeVariableReferenceExpression("value"), new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); + break; + default: + if (typeName == "DateTime") + { + if (nestedContent) + { + fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteString", new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); + } + else + { + fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePrimitiveExpression("yyyy-MM-ddTHH:mm:ss"), new CodePropertyReferenceExpression(new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture"), "DateTimeFormat")))); + } + attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("Convert"), "ToDateTime", new CodeVariableReferenceExpression("value"), new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); + break; + } + + if (enumDeclaration == null) + { + GenerateOutputForEnum(fieldSetStatement, attributeNameMatchStatement, typeNamesToEnumDeclarations[typeName], fieldName, propertyName); + } + else + { + GenerateOutputForEnum(fieldSetStatement, attributeNameMatchStatement, enumDeclaration, fieldName, propertyName); + } + break; + } + + attributeNameMatchStatement.TrueStatements.Add(fieldSetAttrStatement); + + // TODO: Add throw to falseStatements if required field not set. + outputXmlMethod.Statements.Add(fieldSetStatement); + setAttributeMethod.Statements.Add(attributeNameMatchStatement); + + typeDeclaration.Members.Add(propertyMember); + } + + /// + /// Generates output for an enum type. Will generate a switch statement for normal enums, and if statements + /// for a flags enum. + /// + /// If statement to add statements to. + /// If statement to add statements to. + /// Enum declaration for this field. Could be locally defined enum or global. + /// Name of the private field. + /// Name of the property (and XML attribute). + private static void GenerateOutputForEnum(CodeConditionStatement fieldSetStatement, CodeConditionStatement attributeNameMatchStatement, EnumDeclaration enumDeclaration, string fieldName, string propertyName) + { + CodeTypeDeclaration enumParent = enumsToParseMethodClasses[enumDeclaration]; + + if (enumDeclaration.Flags) + { + CodeVariableDeclarationStatement outputValueVariable = new CodeVariableDeclarationStatement(typeof(string), "outputValue", new CodeSnippetExpression("\"\"")); + fieldSetStatement.TrueStatements.Add(outputValueVariable); + + foreach (string key in enumDeclaration.Values) + { + CodeConditionStatement enumIfStatement = new CodeConditionStatement(); + enumIfStatement.Condition = new CodeBinaryOperatorExpression(new CodeBinaryOperatorExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), CodeBinaryOperatorType.BitwiseAnd, new CodePropertyReferenceExpression(new CodeSnippetExpression(enumDeclaration.Name), MakeEnumValue(key))), CodeBinaryOperatorType.IdentityInequality, new CodeSnippetExpression("0")); + CodeConditionStatement lengthIfStatement = new CodeConditionStatement(); + lengthIfStatement.Condition = new CodeBinaryOperatorExpression(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("outputValue"), "Length"), CodeBinaryOperatorType.IdentityInequality, new CodeSnippetExpression("0")); + lengthIfStatement.TrueStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression("outputValue"), new CodeBinaryOperatorExpression(new CodeVariableReferenceExpression("outputValue"), CodeBinaryOperatorType.Add, new CodeSnippetExpression("\" \"")))); + enumIfStatement.TrueStatements.Add(lengthIfStatement); + enumIfStatement.TrueStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression("outputValue"), new CodeBinaryOperatorExpression(new CodeVariableReferenceExpression("outputValue"), CodeBinaryOperatorType.Add, new CodeSnippetExpression(String.Concat("\"", key, "\""))))); + fieldSetStatement.TrueStatements.Add(enumIfStatement); + } + + attributeNameMatchStatement.TrueStatements.Add(new CodeMethodInvokeExpression( + new CodeTypeReferenceExpression(enumParent.Name), + String.Concat("TryParse", enumDeclaration.Name), + new CodeVariableReferenceExpression("value"), + new CodeDirectionExpression(FieldDirection.Out, new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)))); + + fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeSnippetExpression(String.Concat("outputValue")))); + } + else + { + foreach (string key in enumDeclaration.Values) + { + CodeConditionStatement enumOutStatement = new CodeConditionStatement(); + enumOutStatement.Condition = new CodeBinaryOperatorExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), CodeBinaryOperatorType.ValueEquality, new CodePropertyReferenceExpression(new CodeSnippetExpression(enumDeclaration.Name), MakeEnumValue(key))); + enumOutStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeSnippetExpression(String.Concat("\"", key, "\"")))); + fieldSetStatement.TrueStatements.Add(enumOutStatement); + } + + attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement( + new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), + new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(enumParent.Name), + String.Concat("Parse", enumDeclaration.Name), + new CodeVariableReferenceExpression("value")))); + } + } + + /// + /// Generates a summary comment. + /// + /// Comments collection to add the comments to. + /// Content of the comment. + private static void GenerateSummaryComment(CodeCommentStatementCollection comments, string content) + { + using (StringWriter sw = new StringWriter()) + { + XmlTextWriter writer = null; + + // create the comment as xml to ensure proper escaping of special xml characters + try + { + writer = new XmlTextWriter(sw); + writer.Indentation = 0; + + writer.WriteStartElement("summary"); + writer.WriteString(Environment.NewLine); + + string nextComment; + int newlineIndex = content.IndexOf(Environment.NewLine); + int offset = 0; + while (newlineIndex != -1) + { + nextComment = content.Substring(offset, newlineIndex - offset).Trim(); + writer.WriteString(nextComment); + writer.WriteString(Environment.NewLine); + offset = newlineIndex + Environment.NewLine.Length; + newlineIndex = content.IndexOf(Environment.NewLine, offset); + } + nextComment = content.Substring(offset).Trim(); + writer.WriteString(nextComment); + writer.WriteString(Environment.NewLine); + + writer.WriteEndElement(); + } + finally + { + if (null != writer) + { + writer.Close(); + } + } + + // create the comment statements (one per line of xml) + using (StringReader sr = new StringReader(sw.ToString())) + { + string line; + + while (null != (line = sr.ReadLine())) + { + comments.Add(new CodeCommentStatement(line, true)); + } + } + } + } + + /// + /// Gets the CLR type for simple XML type. + /// + /// Plain text name of type. + /// Type corresponding to parameter. + private static Type GetClrTypeByXmlName(string typeName) + { + switch (typeName) + { + case "bool": + return typeof(bool); + case "int": + return typeof(int); + case "long": + return typeof(long); + case "string": + return typeof(string); + default: + return null; + } + } + + /// + /// Processes an XmlSchemaSimpleType into corresponding types. + /// + /// XmlSchemaSimpleType to be processed. + /// CodeNamespace to be used when outputting code. + private static void ProcessSimpleType(XmlSchemaSimpleType simpleType, CodeNamespace codeNamespace) + { + EnumDeclaration enumDeclaration; + string simpleTypeName = simpleType.Name; + string baseTypeName; + + ProcessSimpleType(simpleTypeName, simpleType, false, out enumDeclaration, out baseTypeName); + + simpleTypeNamesToClrTypeNames.Add(simpleTypeName, baseTypeName); + + if (enumDeclaration != null) + { + codeNamespace.Types.Add(enumDeclaration.TypeDeclaration); + typeNamesToEnumDeclarations.Add(simpleTypeName, enumDeclaration); + AddEnumHelperMethods(enumDeclaration, codeNamespace); + } + } + + /// + /// Processes an XmlSchemaSimpleType into corresponding code. + /// + /// Name for the type. + /// XmlSchemaSimpleType to be processed. + /// CodeNamespace to be used when outputting code for global types. + /// CodeTypeDeclaration to be used when outputting code for nested types. + /// Member method for the OutputXml method for nested types. + /// Member method for the SetAttribute method for nested types. + private static void ProcessSimpleType(string simpleTypeName, XmlSchemaSimpleType simpleType, bool nestedType, out EnumDeclaration enumDeclaration, out string baseTypeName) + { + enumDeclaration = null; + baseTypeName = null; + + // XSD supports simpleTypes derived by union, list, or restriction; restrictions can have any + // combination of pattern, enumeration, length, and more; lists can contain any other simpleType. + // XsdGen, in contrast, only supports a limited set of values... + // Unions are weakly supported by just using the first member type + // restrictions must either be all enumeration or a single pattern, a list must be of a + // single simpleType which itself is only a restriction of enumeration. + if (simpleType.Content is XmlSchemaSimpleTypeUnion) + { + XmlSchemaSimpleTypeUnion union = simpleType.Content as XmlSchemaSimpleTypeUnion; + if (union.MemberTypes.Length > 0) + { + baseTypeName = union.MemberTypes[0].Name; + return; + } + else + { + baseTypeName = "string"; + return; + } + } + + bool listType = false; // XSD lists become [Flag]enums in C#... + XmlSchemaSimpleTypeList simpleTypeList = simpleType.Content as XmlSchemaSimpleTypeList; + XmlSchemaSimpleTypeRestriction simpleTypeRestriction = simpleType.Content as XmlSchemaSimpleTypeRestriction; + + if (simpleTypeList != null) + { + baseTypeName = simpleTypeList.ItemTypeName.Name; + + if (String.IsNullOrEmpty(baseTypeName)) + { + simpleTypeRestriction = simpleTypeList.ItemType.Content as XmlSchemaSimpleTypeRestriction; + if (simpleTypeRestriction == null) + { + string appName = typeof(XsdGen).Assembly.GetName().Name; + throw new NotImplementedException(string.Format("{0} does not support a that does not contain a /.", appName)); + } + + listType = true; + } + else + { + // We expect to find an existing enum already declared! + EnumDeclaration existingEnumDeclaration = typeNamesToEnumDeclarations[baseTypeName]; + // TODO: do we need to further alter the Flags setter code because of the helper stuff? + // As far as I can tell, this code is never exercised by our existing XSDs! + existingEnumDeclaration.SetFlags(); + } + } + + if (simpleTypeRestriction == null) + { + string appName = typeof(XsdGen).Assembly.GetName().Name; + throw new NotImplementedException(string.Format("{0} does not understand this simpleType!", appName)); + } + + bool foundPattern = false; + foreach (XmlSchemaFacet facet in simpleTypeRestriction.Facets) + { + XmlSchemaEnumerationFacet enumFacet = facet as XmlSchemaEnumerationFacet; + XmlSchemaPatternFacet patternFacet = facet as XmlSchemaPatternFacet; + + if (enumFacet != null) + { + if (foundPattern) + { + string appName = typeof(XsdGen).Assembly.GetName().Name; + throw new NotImplementedException(string.Format("{0} does not support restrictions containing both and .", appName)); + } + + if (enumDeclaration == null) + { + // For nested types, the simple name comes from the attribute name, with "Type" appended + // to prevent name collision with the attribute member itself. + if (nestedType) + { + simpleTypeName = String.Concat(simpleTypeName, "Type"); + } + baseTypeName = simpleTypeName; + + string typeDocumentation = GetDocumentation(simpleType.Annotation); + enumDeclaration = new EnumDeclaration(simpleTypeName, typeDocumentation); + } + + string documentation = GetDocumentation(enumFacet.Annotation); + enumDeclaration.AddValue(enumFacet.Value, documentation); + } + + if (patternFacet != null) + { + if (enumDeclaration != null) + { + string appName = typeof(XsdGen).Assembly.GetName().Name; + throw new NotImplementedException(string.Format("{0} does not support restrictions containing both and .", appName)); + } + + if (foundPattern) + { + string appName = typeof(XsdGen).Assembly.GetName().Name; + throw new NotImplementedException(string.Format("{0} does not support restrictions multiple elements.", appName)); + } + + foundPattern = true; + } + } + + if (enumDeclaration != null && listType) + { + enumDeclaration.SetFlags(); + } + + if (String.IsNullOrEmpty(baseTypeName)) + { + baseTypeName = (string)simpleTypeNamesToClrTypeNames[simpleTypeRestriction.BaseTypeName.Name]; + } + } + + /// + /// Creates an attribute declaration indicating generated code including the tool name and version. + /// + /// GeneratedCodeAttribute declearation. + private static CodeAttributeDeclaration GetGeneratedCodeAttribute() + { + AssemblyName generatorAssemblyName = typeof(XsdGen).Assembly.GetName(); + return new CodeAttributeDeclaration("GeneratedCode", + new CodeAttributeArgument(new CodePrimitiveExpression(generatorAssemblyName.Name)), + new CodeAttributeArgument(new CodePrimitiveExpression(generatorAssemblyName.Version.ToString()))); + } + + /// + /// Creates a code statement to throw an exception if an argument is null. + /// + /// Name of the argument to check. + /// True to check for null-or-empty instead of just null + /// Code condition statement. + private static CodeConditionStatement GetArgumentNullCheckStatement(string argumentName, bool nullOrEmpty) + { + CodeConditionStatement conditionStatement = new CodeConditionStatement(); + if (nullOrEmpty) + { + conditionStatement.Condition = new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeTypeReferenceExpression("String"), "IsNullOrEmpty"), new CodeVariableReferenceExpression(argumentName)); + } + else + { + conditionStatement.Condition = new CodeBinaryOperatorExpression(new CodePrimitiveExpression(null), CodeBinaryOperatorType.ValueEquality, new CodeVariableReferenceExpression(argumentName)); + } + + conditionStatement.TrueStatements.Add(new CodeThrowExceptionStatement(new CodeObjectCreateExpression("ArgumentNullException", new CodeSnippetExpression(String.Concat("\"", argumentName, "\""))))); + return conditionStatement; + } + + /// + /// Creates an attribute declaration to suppress a particular code-analysis message. + /// + /// Code analysis category, such as "Microsoft.Design" + /// Code analysis ID number. + /// SuppressMessageAttribute declaration. + private static CodeAttributeDeclaration GetCodeAnalysisSuppressionAttribute(string category, string checkId) + { + return new CodeAttributeDeclaration("SuppressMessage", + new CodeAttributeArgument(new CodePrimitiveExpression(category)), + new CodeAttributeArgument(new CodePrimitiveExpression(checkId))); + } + + /// + /// Class representing an enum declaration. + /// + internal class EnumDeclaration + { + private string enumTypeName; + private CodeTypeDeclaration declaration; + private bool flags; + private StringCollection enumValues; + + /// + /// Creates a new enum declaration with the given name. + /// + /// Name of the type for the enum. + /// Documentation for the enum type. + public EnumDeclaration(string enumTypeName, string documentation) + { + this.enumTypeName = enumTypeName; + + this.declaration = new CodeTypeDeclaration(enumTypeName); + this.declaration.CustomAttributes.Add(GetGeneratedCodeAttribute()); + this.declaration.Attributes = MemberAttributes.Public; + this.declaration.IsEnum = true; + + if (documentation != null) + { + GenerateSummaryComment(this.declaration.Comments, documentation); + } + + this.enumValues = new StringCollection(); + } + + public CodeTypeDeclaration TypeDeclaration + { + get { return this.declaration; } + } + + /// + /// Gets the enumeration values. + /// + /// The enumeration values. + public ICollection Values + { + get { return this.enumValues; } + } + + /// + /// Gets the enumeration name. + /// + /// The enumeration name. + public string Name + { + get { return this.enumTypeName; } + } + + /// + /// Gets the enumeration flags property. + /// + /// Whether the enumeration is a [Flags] type. + public bool Flags + { + get { return this.flags; } + } + + /// + /// Sets the [Flags] property on the enumeration. Once set, this cannot be undone. + /// + public void SetFlags() + { + if (this.flags) + { + return; + } + + this.flags = true; + + this.declaration.CustomAttributes.Add(new CodeAttributeDeclaration("Flags")); + SwitchToNoneValue(); + + int enumValue = 0; + foreach (CodeMemberField enumField in this.declaration.Members) + { + enumField.InitExpression = new CodeSnippetExpression(enumValue.ToString()); + if (enumValue == 0) + { + enumValue = 1; + } + else + { + enumValue *= 2; + } + } + } + + private void InjectIllegalAndNotSetValues() + { + CodeMemberField memberIllegal = new CodeMemberField(typeof(int), "IllegalValue"); + CodeMemberField memberNotSet = new CodeMemberField(typeof(int), "NotSet"); + + memberIllegal.InitExpression = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(int)), "MaxValue"); + // Using "-1" for "NotSet" ensure that the next value is zero, which is consistent + // with older (3.0) behavior. + memberNotSet.InitExpression = new CodePrimitiveExpression(-1); + + this.declaration.Members.Insert(0, memberIllegal); + this.declaration.Members.Insert(1, memberNotSet); + } + + private void SwitchToNoneValue() + { + if (this.enumValues.Count > 0) + { + // Remove the "IllegalValue" and "NotSet" values first. + this.declaration.Members.RemoveAt(0); + this.declaration.Members.RemoveAt(0); + + CodeMemberField memberNone = new CodeMemberField(typeof(int), "None"); + memberNone.InitExpression = new CodePrimitiveExpression(0); + + this.declaration.Members.Insert(0, memberNone); + } + } + + /// + /// Add a value to the enumeration. + /// + /// The value to add. + public void AddValue(string enumValue, string documentation) + { + if (this.enumValues.Count == 0) + { + InjectIllegalAndNotSetValues(); + } + + this.enumValues.Add(enumValue); + CodeMemberField memberField = new CodeMemberField(typeof(int), MakeEnumValue(enumValue)); + //memberField.Attributes + this.declaration.Members.Add(memberField); + if (documentation != null) + { + GenerateSummaryComment(memberField.Comments, documentation); + } + } + } + + private static void AddEnumHelperMethods(EnumDeclaration enumDeclaration, CodeNamespace codeNamespace) + { + if (enumHelperClass == null) + { + enumHelperClass = new CodeTypeDeclaration("Enums"); + enumHelperClass.CustomAttributes.Add(GetGeneratedCodeAttribute()); + // The static and final attributes don't seem to get applied, but we'd prefer if they were. + enumHelperClass.Attributes = MemberAttributes.Public | MemberAttributes.Static | MemberAttributes.Final; + codeNamespace.Types.Add(enumHelperClass); + } + + AddEnumHelperMethods(enumDeclaration, enumHelperClass); + } + + private static void AddEnumHelperMethods(EnumDeclaration enumDeclaration, CodeTypeDeclaration parentType) + { + CodeTypeReference stringType = new CodeTypeReference(typeof(string)); + CodeTypeReference boolType = new CodeTypeReference(typeof(bool)); + CodeTypeReference enumType = new CodeTypeReference(typeof(Enum)); + CodeTypeReference newEnumType = new CodeTypeReference(enumDeclaration.Name); + + CodePrimitiveExpression falseValue = new CodePrimitiveExpression(false); + CodePrimitiveExpression trueValue = new CodePrimitiveExpression(true); + CodeMethodReturnStatement returnFalse = new CodeMethodReturnStatement(falseValue); + CodeMethodReturnStatement returnTrue = new CodeMethodReturnStatement(trueValue); + + string parseMethodName = String.Concat("Parse", enumDeclaration.Name); + string tryParseMethodName = String.Concat("TryParse", enumDeclaration.Name); + + CodeFieldReferenceExpression defaultEnumValue = null; + CodeFieldReferenceExpression illegalEnumValue = null; + bool addParse = true; + if (enumDeclaration.Flags) + { + defaultEnumValue = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(newEnumType), "None"); + illegalEnumValue = defaultEnumValue; + // Because there's no "IllegalValue" for [Flags] enums, we can't create the Parse() + // method. We can still create the TryParse() method, though! + addParse = false; + } + else + { + defaultEnumValue = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(newEnumType), "NotSet"); + illegalEnumValue = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(newEnumType), "IllegalValue"); + } + + if (addParse) + { + CodeMemberMethod parseNewEnum = new CodeMemberMethod(); + GenerateSummaryComment(parseNewEnum.Comments, String.Format("Parses a {0} from a string.", enumDeclaration.Name)); + parseNewEnum.Attributes = MemberAttributes.Public | MemberAttributes.Static; + parseNewEnum.Name = parseMethodName; + parseNewEnum.ReturnType = newEnumType; + parseNewEnum.Parameters.Add(new CodeParameterDeclarationExpression(stringType, "value")); + + parseNewEnum.Statements.Add(new CodeVariableDeclarationStatement(newEnumType, "parsedValue")); + + // Just delegate to the TryParse version... + parseNewEnum.Statements.Add(new CodeMethodInvokeExpression( + new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(parentType.Name), tryParseMethodName), + new CodeArgumentReferenceExpression("value"), + new CodeDirectionExpression(FieldDirection.Out, new CodeVariableReferenceExpression("parsedValue")))); + + parseNewEnum.Statements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression("parsedValue"))); + parentType.Members.Add(parseNewEnum); + } + + CodeMemberMethod tryParseNewEnum = new CodeMemberMethod(); + GenerateSummaryComment(tryParseNewEnum.Comments, String.Format("Tries to parse a {0} from a string.", enumDeclaration.Name)); + tryParseNewEnum.Attributes = MemberAttributes.Public | MemberAttributes.Static; + tryParseNewEnum.Name = tryParseMethodName; + tryParseNewEnum.ReturnType = boolType; + CodeParameterDeclarationExpression valueDeclaration = new CodeParameterDeclarationExpression(stringType, "value"); + CodeParameterDeclarationExpression parsedValueDeclaration = new CodeParameterDeclarationExpression(newEnumType, "parsedValue"); + parsedValueDeclaration.Direction = FieldDirection.Out; + tryParseNewEnum.Parameters.Add(valueDeclaration); + tryParseNewEnum.Parameters.Add(parsedValueDeclaration); + + CodeArgumentReferenceExpression value = new CodeArgumentReferenceExpression(valueDeclaration.Name); + CodeArgumentReferenceExpression parsedValue = new CodeArgumentReferenceExpression(parsedValueDeclaration.Name); + + tryParseNewEnum.Statements.Add(new CodeAssignStatement(parsedValue, defaultEnumValue)); + + tryParseNewEnum.Statements.Add(new CodeConditionStatement( + new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(stringType), "IsNullOrEmpty"), value), + returnFalse)); + + // The structure is similar, but distinct, for regular and flag-style enums. In particular, + // for a flags-style enum we have to be able to parse multiple values, separated by + // spaces, and each value is bitwise-OR'd together. + CodeStatementCollection nestedIfParent = tryParseNewEnum.Statements; + CodeExpression valueToTest = value; + + // For Flags-style enums, we need to loop over the space-separated values... + if (enumDeclaration.Flags) + { + CodeVariableDeclarationStatement split = new CodeVariableDeclarationStatement(typeof(string[]), "splitValue", + new CodeMethodInvokeExpression(value, "Split", + new CodeMethodInvokeExpression(new CodePrimitiveExpression(" \t\r\n"), "ToCharArray"), + new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(StringSplitOptions)), "RemoveEmptyEntries"))); + tryParseNewEnum.Statements.Add(split); + + CodeIterationStatement flagLoop = new CodeIterationStatement( + new CodeVariableDeclarationStatement(typeof(IEnumerator), "enumerator", + new CodeMethodInvokeExpression(new CodeVariableReferenceExpression(split.Name), "GetEnumerator")), + new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("enumerator"), "MoveNext"), + new CodeSnippetStatement("")); + tryParseNewEnum.Statements.Add(flagLoop); + + CodeVariableDeclarationStatement currentValue = new CodeVariableDeclarationStatement(typeof(string), "currentValue", + new CodeCastExpression(stringType, + new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("enumerator"), "Current"))); + flagLoop.Statements.Add(currentValue); + valueToTest = new CodeVariableReferenceExpression(currentValue.Name); + + nestedIfParent = flagLoop.Statements; + } + + // We can't just Enum.Parse, because some values are also keywords (like 'string', 'int', 'default'), + // and these get generated as '@'-prefixed values. Instead, we 'switch' on the value and do it manually. + // Actually, we if/else, because CodeDom doesn't support 'switch'! Also, we nest the successive 'if's + // in order to short-circuit the parsing as soon as there's a match. + foreach (string enumValue in enumDeclaration.Values) + { + CodeFieldReferenceExpression enumValueReference = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(newEnumType), MakeEnumValue(enumValue)); + CodeConditionStatement ifStatement = new CodeConditionStatement( + new CodeBinaryOperatorExpression(new CodePrimitiveExpression(enumValue), CodeBinaryOperatorType.ValueEquality, valueToTest)); + if (enumDeclaration.Flags) + { + ifStatement.TrueStatements.Add(new CodeAssignStatement(parsedValue, + new CodeBinaryOperatorExpression(parsedValue, CodeBinaryOperatorType.BitwiseOr, enumValueReference))); + } + else + { + ifStatement.TrueStatements.Add(new CodeAssignStatement(parsedValue, enumValueReference)); + } + nestedIfParent.Add(ifStatement); + nestedIfParent = ifStatement.FalseStatements; + } + + // Finally, if we didn't find a match, it's illegal (or none, for flags)! + nestedIfParent.Add(new CodeAssignStatement(parsedValue, illegalEnumValue)); + nestedIfParent.Add(returnFalse); + + tryParseNewEnum.Statements.Add(returnTrue); + + parentType.Members.Add(tryParseNewEnum); + + enumsToParseMethodClasses.Add(enumDeclaration, parentType); + } + } +} diff --git a/src/WixBuildTools.XsdGen/WixBuildTools.XsdGen.csproj b/src/WixBuildTools.XsdGen/WixBuildTools.XsdGen.csproj new file mode 100644 index 00000000..430e290d --- /dev/null +++ b/src/WixBuildTools.XsdGen/WixBuildTools.XsdGen.csproj @@ -0,0 +1,22 @@ + + + + + net461 + true + false + Exe + + + + + + + + + + + + + + diff --git a/src/WixBuildTools.XsdGen/XsdGen.cs b/src/WixBuildTools.XsdGen/XsdGen.cs new file mode 100644 index 00000000..a1374df3 --- /dev/null +++ b/src/WixBuildTools.XsdGen/XsdGen.cs @@ -0,0 +1,124 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Tools +{ + using System; + using System.CodeDom; + using System.CodeDom.Compiler; + using System.Collections; + using System.IO; + using System.Xml; + using System.Xml.Schema; + using Microsoft.CSharp; + using WixToolset; + + /// + /// Generates a strongly-typed C# class from an XML schema (XSD). + /// + public class XsdGen + { + private string xsdFile; + private string outFile; + private string outputNamespace; + private string commonNamespace; + private bool showHelp; + + /// + /// Constructor for the XsdGen class. + /// + /// Command-line arguments passed to the program. + private XsdGen(string[] args) + { + this.ParseCommandlineArgs(args); + + // show usage information + if (this.showHelp) + { + Console.WriteLine("usage: XsdGen.exe .xsd []"); + return; + } + + // ensure that the schema file exists + if (!File.Exists(this.xsdFile)) + { + throw new ApplicationException(String.Format("Schema file does not exist: '{0}'.", this.xsdFile)); + } + + XmlSchema document = null; + using (StreamReader xsdFileReader = new StreamReader(this.xsdFile)) + { + document = XmlSchema.Read(xsdFileReader, new ValidationEventHandler(this.ValidationHandler)); + } + + CodeCompileUnit codeCompileUnit = StronglyTypedClasses.Generate(document, this.outputNamespace, this.commonNamespace); + + using (CSharpCodeProvider codeProvider = new CSharpCodeProvider()) + { + ICodeGenerator generator = codeProvider.CreateGenerator(); + + CodeGeneratorOptions options = new CodeGeneratorOptions(); + options.BlankLinesBetweenMembers = true; + options.BracingStyle = "C"; + options.IndentString = " "; + + using (StreamWriter csharpFileWriter = new StreamWriter(this.outFile)) + { + generator.GenerateCodeFromCompileUnit(codeCompileUnit, csharpFileWriter, options); + } + } + } + + /// + /// The main entry point for the application. + /// + /// The command line arguments. + /// The error code. + [STAThread] + public static int Main(string[] args) + { + try + { + XsdGen xsdGen = new XsdGen(args); + } + catch (Exception e) + { + Console.WriteLine("XsdGen.exe : fatal error MSF0000: {0}\r\n\r\nStack Trace:\r\n{1}", e.Message, e.StackTrace); + return 1; + } + + return 0; + } + + /// + /// Validation event handler. + /// + /// Sender for the event. + /// Event args. + public void ValidationHandler(object sender, ValidationEventArgs e) + { + } + + /// + /// Parse the command line arguments. + /// + /// Command-line arguments. + private void ParseCommandlineArgs(string[] args) + { + if (3 > args.Length) + { + this.showHelp = true; + } + else + { + this.xsdFile = args[0]; + this.outFile = args[1]; + this.outputNamespace = args[2]; + + if (args.Length >= 4) + { + this.commonNamespace = args[3]; + } + } + } + } +} diff --git a/src/WixBuildTools.XsdGen/build/WixBuildTools.XsdGen.targets b/src/WixBuildTools.XsdGen/build/WixBuildTools.XsdGen.targets new file mode 100644 index 00000000..ca1b89f6 --- /dev/null +++ b/src/WixBuildTools.XsdGen/build/WixBuildTools.XsdGen.targets @@ -0,0 +1,67 @@ + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + + + + + + $(MSBuildThisFileDirectory)..\tools\full\ + + + + + + + + XsdGen; + $(PrepareResourcesDependsOn) + + + + + + $(IntermediateOutputPath)%(XsdGenSource.Filename).cs + %(XsdGenSource.CommonNamespace) + + + + + + + + + + + %(XsdGenCsFile.Filename)%(XsdGenCsFile.Extension) + + + + + diff --git a/src/WixBuildTools.XsdGen/buildCrossTargeting/WixBuildTools.XsdGen.targets b/src/WixBuildTools.XsdGen/buildCrossTargeting/WixBuildTools.XsdGen.targets new file mode 100644 index 00000000..58692095 --- /dev/null +++ b/src/WixBuildTools.XsdGen/buildCrossTargeting/WixBuildTools.XsdGen.targets @@ -0,0 +1,6 @@ + + + + + + -- cgit v1.2.3-55-g6feb