From 4ebc33174c02e1c9f5693b5ef38ecfe3292c687f Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 11 Aug 2017 00:39:04 -0700 Subject: Move to .NET Core 2.0 --- src/WixToolset.Data/Serialize/CodeDomInterfaces.cs | 96 ++++ src/WixToolset.Data/Serialize/CodeDomReader.cs | 161 ++++++ src/WixToolset.Data/Serialize/ElementCollection.cs | 617 +++++++++++++++++++++ 3 files changed, 874 insertions(+) create mode 100644 src/WixToolset.Data/Serialize/CodeDomInterfaces.cs create mode 100644 src/WixToolset.Data/Serialize/CodeDomReader.cs create mode 100644 src/WixToolset.Data/Serialize/ElementCollection.cs (limited to 'src/WixToolset.Data/Serialize') diff --git a/src/WixToolset.Data/Serialize/CodeDomInterfaces.cs b/src/WixToolset.Data/Serialize/CodeDomInterfaces.cs new file mode 100644 index 00000000..36a05202 --- /dev/null +++ b/src/WixToolset.Data/Serialize/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.Data.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. + /// + /// XmlWriter 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/WixToolset.Data/Serialize/CodeDomReader.cs b/src/WixToolset.Data/Serialize/CodeDomReader.cs new file mode 100644 index 00000000..76013936 --- /dev/null +++ b/src/WixToolset.Data/Serialize/CodeDomReader.cs @@ -0,0 +1,161 @@ +// 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.Data.Serialize +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + 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. + [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] + 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(WixDataStrings.EXP_MultipleRootElementsFoundInFile); + } + + schemaElement = this.CreateObjectFromElement(element); + this.ParseObjectFromElement(schemaElement, element); + } + } + return schemaElement; + } + + /// + /// Sets an attribute on an ISchemaElement. + /// + /// Schema element to set attribute on. + /// Name of the attribute to set. + /// Value to set on the attribute. + [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] + private static void SetAttributeOnObject(ISchemaElement schemaElement, string name, string value) + { + ISetAttributes setAttributes = schemaElement as ISetAttributes; + if (setAttributes == null) + { + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_ISchemaElementDoesnotImplementISetAttribute, schemaElement.GetType().FullName)); + } + else + { + setAttributes.SetAttribute(name, value); + } + } + + /// + /// Parses an ISchemaElement from the XmlElement. + /// + /// ISchemaElement to fill in. + /// XmlElement to parse from. + [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] + private void ParseObjectFromElement(ISchemaElement schemaElement, XmlElement element) + { + foreach (XmlAttribute attribute in element.Attributes) + { + 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(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_ISchemaElementDoesnotImplementICreateChildren, element.LocalName)); + } + else + { + childSchemaElement = createChildren.CreateChild(childElement.LocalName); + } + + if (childSchemaElement == null) + { + childSchemaElement = this.CreateObjectFromElement(childElement); + if (childSchemaElement == null) + { + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_XmlElementDoesnotHaveISchemaElement, childElement.LocalName)); + } + } + + this.ParseObjectFromElement(childSchemaElement, childElement); + IParentElement parentElement = (IParentElement)schemaElement; + parentElement.AddChild(childSchemaElement); + } + else + { + XmlText childText = node as XmlText; + if (childText != null) + { + SetAttributeOnObject(schemaElement, "Content", childText.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, StringComparison.Ordinal) + && typeof(ISchemaElement).IsAssignableFrom(type)) + { + schemaElement = (ISchemaElement)Activator.CreateInstance(type); + } + } + } + return schemaElement; + } + } +} diff --git a/src/WixToolset.Data/Serialize/ElementCollection.cs b/src/WixToolset.Data/Serialize/ElementCollection.cs new file mode 100644 index 00000000..1d64c5eb --- /dev/null +++ b/src/WixToolset.Data/Serialize/ElementCollection.cs @@ -0,0 +1,617 @@ +// 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.Data.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 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(); + } + + /// + /// 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, + WixDataStrings.EXP_ElementOfTypeIsNotValidForThisCollection, + 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, + WixDataStrings.EXP_ElementOfTypeIsNotValidForThisCollection, + 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, + WixDataStrings.EXP_TypeIsNotValidForThisCollection, + 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. + protected CollectionItem(Type elementType) + { + this.elementType = elementType; + this.elements = new ArrayList(); + } + + /// + /// Gets the type of this collection's items. + /// + /// The type of this collection's items. + public Type ElementType + { + get { return this.elementType; } + } + + /// + /// Gets the elements of this collection. + /// + /// 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, + WixDataStrings.EXP_ElementIsSubclassOfDifferentType, + 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, + WixDataStrings.EXP_ElementIsSubclassOfDifferentType, + 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 + { + /// + /// Creates a new sequence item. + /// + /// Type of the created item. + public SequenceItem(Type elementType) + : base(elementType) + { + } + } + + /// + /// 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, + WixDataStrings.EXP_ElementMustBeChoiceItemOrSequenceItem, + 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 elementCollection) + { + if (elementCollection.Count <= 0) + { + throw new ArgumentException(String.Format( + CultureInfo.InvariantCulture, + WixDataStrings.EXP_CollectionMustHaveAtLeastOneElement, + elementCollection.Count)); + } + + CollectionTuple tuple = new CollectionTuple(elementCollection); + 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; } + } + } + } + } +} -- cgit v1.2.3-55-g6feb