diff --git a/XmlSchemaClassGenerator/Extensions.cs b/XmlSchemaClassGenerator/Extensions.cs index b97d30bc..14cdb241 100644 --- a/XmlSchemaClassGenerator/Extensions.cs +++ b/XmlSchemaClassGenerator/Extensions.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Xml.Schema; namespace XmlSchemaClassGenerator @@ -12,31 +9,18 @@ public static class Extensions { public static XmlSchema GetSchema(this XmlSchemaObject xmlSchemaObject) { - while (xmlSchemaObject != null && !(xmlSchemaObject is XmlSchema)) + while (xmlSchemaObject is not null and not XmlSchema) xmlSchemaObject = xmlSchemaObject.Parent; return (XmlSchema)xmlSchemaObject; } - public static PropertyValueTypeCode GetPropertyValueTypeCode(this TypeModel model) + public static PropertyValueTypeCode GetPropertyValueTypeCode(this TypeModel model) => model switch { - if (model is not SimpleModel simpleType) - { - if (!(model is EnumModel)) - { - return PropertyValueTypeCode.Other; - } - return PropertyValueTypeCode.ValueType; - } - if (simpleType.ValueType.IsArray) - { - return PropertyValueTypeCode.Array; - } - if (simpleType.ValueType.IsValueType) - { - return PropertyValueTypeCode.ValueType; - } - return PropertyValueTypeCode.Other; - } + SimpleModel { ValueType.IsArray: true } => PropertyValueTypeCode.Array, + SimpleModel { ValueType.IsValueType: true } => PropertyValueTypeCode.ValueType, + SimpleModel or not EnumModel => PropertyValueTypeCode.Other, + _ => PropertyValueTypeCode.ValueType + }; public static IEnumerable DistinctBy(this IEnumerable source, Func propertySelector) { @@ -45,17 +29,9 @@ public static IEnumerable DistinctBy(this IEnumerable Real = xs; + + public XmlSchemaAttribute Real { get; } + + //public string Name => Real.Name; + public string DefaultValue => Real.DefaultValue; + public string FixedValue => Real.FixedValue; + public XmlSchemaForm Form => Real.Form; + public XmlQualifiedName QualifiedName => Real.QualifiedName; + public XmlQualifiedName RefName => Real.RefName; + //public XmlQualifiedName SchemaTypeName => Real.SchemaTypeName; + //public XmlSchemaSimpleType SchemaType => Real.SchemaType; + public XmlSchemaSimpleType AttributeSchemaType => Real.AttributeSchemaType; + + public XmlSchemaAnnotated Base => Real; + + public XmlSchemaForm FormDefault => Base.GetSchema().AttributeFormDefault; + + //XmlSchemaType IXmlSchemaNode.SchemaType => SchemaType; + + //XmlSchemaType IXmlSchemaNode.NodeSchemaType => AttributeSchemaType; + + public XmlSchemaUse Use => Real.Use; + + public static implicit operator XmlSchemaAttributeEx(XmlSchemaAttribute xs) => new(xs); + public static implicit operator XmlSchemaAttribute(XmlSchemaAttributeEx ex) => ex.Real; + } + + public sealed class XmlSchemaElementEx : IXmlSchemaNode + { + private XmlSchemaElementEx(XmlSchemaElement xs) => Real = xs; + + public XmlSchemaElement Real { get; } + + //public string Name => Real.Name; + public string DefaultValue => Real.DefaultValue; + public string FixedValue => Real.FixedValue; + public XmlSchemaForm Form => Real.Form; + public XmlQualifiedName QualifiedName => Real.QualifiedName; + public XmlQualifiedName RefName => Real.RefName; + //public XmlQualifiedName SchemaTypeName => Real.SchemaTypeName; + //public XmlSchemaType SchemaType => Real.SchemaType; + public XmlSchemaType ElementSchemaType => Real.ElementSchemaType; + + public XmlSchemaAnnotated Base => Real; + + public XmlSchemaForm FormDefault => Base.GetSchema().ElementFormDefault; + + //XmlSchemaType IXmlSchemaNode.SchemaType => SchemaType; + + //XmlSchemaType IXmlSchemaNode.NodeSchemaType => ElementSchemaType; + + public bool IsNillable => Real.IsNillable; + + public static implicit operator XmlSchemaElementEx(XmlSchemaElement xs) => new(xs); + public static implicit operator XmlSchemaElement(XmlSchemaElementEx ex) => ex.Real; + } +} \ No newline at end of file diff --git a/XmlSchemaClassGenerator/ModelBuilder.cs b/XmlSchemaClassGenerator/ModelBuilder.cs index 7202c7c8..d4d7c2aa 100644 --- a/XmlSchemaClassGenerator/ModelBuilder.cs +++ b/XmlSchemaClassGenerator/ModelBuilder.cs @@ -5,7 +5,6 @@ using System.Xml; using System.Xml.Linq; using System.Xml.Schema; -using System.Xml.Serialization; namespace XmlSchemaClassGenerator { @@ -24,6 +23,9 @@ internal class ModelBuilder private string BuildKey(XmlSchemaAnnotated annotated, XmlQualifiedName name) => $"{annotated.GetType()}:{annotated.SourceUri}:{annotated.LineNumber}:{annotated.LinePosition}:{name}"; + private void SetType(XmlSchemaAnnotated annotated, XmlQualifiedName name, TypeModel type) + => Types[BuildKey(annotated, name)] = type; + public ModelBuilder(GeneratorConfiguration configuration, XmlSchemaSet set) { _configuration = configuration; @@ -40,8 +42,7 @@ public ModelBuilder(GeneratorConfiguration configuration, XmlSchemaSet set) UseDataTypeAttribute = false }; - var key = BuildKey(new XmlSchemaComplexType(), AnyType); - Types[key] = objectModel; + SetType(new XmlSchemaComplexType(), AnyType, objectModel); AttributeGroups = set.Schemas().Cast().SelectMany(s => s.AttributeGroups.Values.Cast()) .DistinctBy(g => g.QualifiedName.ToString()) @@ -53,16 +54,15 @@ public ModelBuilder(GeneratorConfiguration configuration, XmlSchemaSet set) var dependencyOrder = new List(); var seenSchemas = new HashSet(); foreach (var schema in set.Schemas().Cast()) - { ResolveDependencies(schema, dependencyOrder, seenSchemas); - } foreach (var schema in dependencyOrder) { - var types = set.GlobalTypes.Values.Cast().Where(s => s.GetSchema() == schema); - CreateTypes(types); - var elements = set.GlobalElements.Values.Cast().Where(s => s.GetSchema() == schema); - CreateElements(elements); + foreach (var globalType in set.GlobalTypes.Values.Cast().Where(s => s.GetSchema() == schema)) + CreateTypeModel(globalType.QualifiedName, globalType); + + foreach (var rootElement in set.GlobalElements.Values.Cast().Where(s => s.GetSchema() == schema)) + CreateElement(rootElement); } CreateSubstitutes(); @@ -112,7 +112,7 @@ private void CreateSubstitutes() if (prop.XmlSchemaName != null) { - var substitutes = GetSubstitutedElements(prop.XmlSchemaName).ToList(); + var substitutes = GetSubstitutedElements(prop.XmlSchemaName); if (_configuration.SeparateSubstitutes) { @@ -130,7 +130,7 @@ private void CreateSubstitutes() } else { - prop.Substitutes = substitutes; + prop.Substitutes.AddRange(substitutes); } } } @@ -148,9 +148,7 @@ private void AddXmlRootAttributeToAmbiguousTypes() continue; } foreach (var typeModel in types) - { typeModel.RootElementName = typeModel.GetQualifiedName(); - } } } @@ -165,9 +163,7 @@ private void RemoveDuplicateInterfaceProperties() { var baseProperties = baseInterfaceTypeProperties.ToList(); foreach (var baseProperty in baseProperties.Where(baseProperty => parentProperty.Name == baseProperty.Name && parentProperty.Type.Name == baseProperty.Type.Name)) - { baseInterfaceTypeProperties.Remove(baseProperty); - } } } } @@ -209,9 +205,7 @@ private void PromoteInterfacePropertiesToCollection() if (derivedProperties.Any(p => p.IsCollection)) { foreach (var derivedProperty in derivedProperties.Where(p => !p.IsCollection)) - { derivedProperty.IsCollection = true; - } interfaceProperty.IsCollection = true; } @@ -222,14 +216,10 @@ private void PromoteInterfacePropertiesToCollection() private static void RenameInterfacePropertyInBaseClasses(InterfaceModel interfaceModel, ReferenceTypeModel implementationClass, PropertyModel interfaceProperty, string newName) { - foreach (var interfaceModelImplementationClass in interfaceModel.AllDerivedReferenceTypes().Where(c => - c != implementationClass)) + foreach (var derivedClass in interfaceModel.AllDerivedReferenceTypes().Where(c => c != implementationClass)) { - foreach (var propertyModel in interfaceModelImplementationClass.Properties.Where(p => - p.Name == interfaceProperty.Name)) - { + foreach (var propertyModel in derivedClass.Properties.Where(p => p.Name == interfaceProperty.Name)) propertyModel.Name = newName; - } } } @@ -254,126 +244,113 @@ private void ResolveDependencies(XmlSchema schema, List dependencyOrd dependencyOrder.Add(schema); } - - private void CreateTypes(IEnumerable types) + private void CreateElement(XmlSchemaElement rootElement) { - foreach (var globalType in types) + var qualifiedName = rootElement.ElementSchemaType.QualifiedName; + if (qualifiedName.IsEmpty) + qualifiedName = rootElement.QualifiedName; + var type = CreateTypeModel(qualifiedName, rootElement.ElementSchemaType); + ClassModel derivedClassModel = null; + + if (type.RootElementName != null || type.IsAbstractRoot) { - CreateTypeModel(globalType, globalType.QualifiedName); + if (type is ClassModel classModel) + derivedClassModel = CreateDerivedRootClass(rootElement, type, classModel); + else + SetType(rootElement, rootElement.QualifiedName, type); } - } - - private void CreateElements(IEnumerable elements) - { - foreach (var rootElement in elements) + else { - var qualifiedName = rootElement.ElementSchemaType.QualifiedName; - if (qualifiedName.IsEmpty) { qualifiedName = rootElement.QualifiedName; } - var type = CreateTypeModel(rootElement.ElementSchemaType, qualifiedName); - ClassModel derivedClassModel = null; + if (type is ClassModel classModel) + classModel.Documentation.AddRange(GetDocumentation(rootElement)); + + type.RootElement = rootElement; + type.RootElementName = rootElement.QualifiedName; + } - if (type.RootElementName != null || type.IsAbstractRoot) + if (!rootElement.SubstitutionGroup.IsEmpty) + { + if (!SubstitutionGroups.TryGetValue(rootElement.SubstitutionGroup, out var substitutes)) { - if (type is ClassModel classModel) - { - // There is already another global element with this type. - // Need to create an empty derived class. + substitutes = new HashSet(); + SubstitutionGroups.Add(rootElement.SubstitutionGroup, substitutes); + } - var elementSource = CodeUtilities.CreateUri(rootElement.SourceUri); + substitutes.Add(new Substitute { Element = rootElement, Type = derivedClassModel ?? type }); + } + } - derivedClassModel = new ClassModel(_configuration) - { - Name = _configuration.NamingProvider.RootClassNameFromQualifiedName(rootElement.QualifiedName, rootElement), - Namespace = CreateNamespaceModel(elementSource, rootElement.QualifiedName) - }; + private ClassModel CreateDerivedRootClass(XmlSchemaElement rootElement, TypeModel type, ClassModel classModel) + { + ClassModel derivedClassModel; + // There is already another global element with this type. + // Need to create an empty derived class. - derivedClassModel.Documentation.AddRange(GetDocumentation(rootElement)); + var elementSource = CodeUtilities.CreateUri(rootElement.SourceUri); - if (derivedClassModel.Namespace != null) - { - derivedClassModel.Name = derivedClassModel.Namespace.GetUniqueTypeName(derivedClassModel.Name); - derivedClassModel.Namespace.Types[derivedClassModel.Name] = derivedClassModel; - } + derivedClassModel = new ClassModel(_configuration) + { + Name = _configuration.NamingProvider.RootClassNameFromQualifiedName(rootElement.QualifiedName, rootElement), + Namespace = CreateNamespaceModel(elementSource, rootElement.QualifiedName) + }; - var key = BuildKey(rootElement, rootElement.QualifiedName); - Types[key] = derivedClassModel; + derivedClassModel.Documentation.AddRange(GetDocumentation(rootElement)); - derivedClassModel.BaseClass = classModel; - ((ClassModel)derivedClassModel.BaseClass).DerivedTypes.Add(derivedClassModel); + if (derivedClassModel.Namespace != null) + { + derivedClassModel.Name = derivedClassModel.Namespace.GetUniqueTypeName(derivedClassModel.Name); + derivedClassModel.Namespace.Types[derivedClassModel.Name] = derivedClassModel; + } - derivedClassModel.RootElementName = rootElement.QualifiedName; + SetType(rootElement, rootElement.QualifiedName, derivedClassModel); - if (!type.IsAbstractRoot) - { - // Also create an empty derived class for the original root element + derivedClassModel.BaseClass = classModel; + ((ClassModel)derivedClassModel.BaseClass).DerivedTypes.Add(derivedClassModel); - var originalClassModel = new ClassModel(_configuration) - { - Name = _configuration.NamingProvider.RootClassNameFromQualifiedName(type.RootElementName, rootElement), - Namespace = classModel.Namespace - }; + derivedClassModel.RootElementName = rootElement.QualifiedName; - originalClassModel.Documentation.AddRange(classModel.Documentation); - classModel.Documentation.Clear(); + if (!type.IsAbstractRoot) + CreateOriginalRootClass(rootElement, type, classModel); - if (originalClassModel.Namespace != null) - { - originalClassModel.Name = originalClassModel.Namespace.GetUniqueTypeName(originalClassModel.Name); - originalClassModel.Namespace.Types[originalClassModel.Name] = originalClassModel; - } + return derivedClassModel; + } - if (classModel.XmlSchemaName != null && !classModel.XmlSchemaName.IsEmpty) - { - key = BuildKey(classModel.RootElement, classModel.XmlSchemaName); - Types[key] = originalClassModel; - } + private void CreateOriginalRootClass(XmlSchemaElement rootElement, TypeModel type, ClassModel classModel) + { + // Also create an empty derived class for the original root element - originalClassModel.BaseClass = classModel; - ((ClassModel)originalClassModel.BaseClass).DerivedTypes.Add(originalClassModel); + var originalClassModel = new ClassModel(_configuration) + { + Name = _configuration.NamingProvider.RootClassNameFromQualifiedName(type.RootElementName, rootElement), + Namespace = classModel.Namespace + }; - originalClassModel.RootElementName = type.RootElementName; + originalClassModel.Documentation.AddRange(classModel.Documentation); + classModel.Documentation.Clear(); - if (classModel.RootElement.SubstitutionGroup != null - && SubstitutionGroups.TryGetValue(classModel.RootElement.SubstitutionGroup, out var substitutes)) - { - foreach (var substitute in substitutes.Where(s => s.Element == classModel.RootElement)) - { - substitute.Type = originalClassModel; - } - } + if (originalClassModel.Namespace != null) + { + originalClassModel.Name = originalClassModel.Namespace.GetUniqueTypeName(originalClassModel.Name); + originalClassModel.Namespace.Types[originalClassModel.Name] = originalClassModel; + } - classModel.RootElementName = null; - classModel.IsAbstractRoot = true; - } - } - else - { - var key = BuildKey(rootElement, rootElement.QualifiedName); - Types[key] = type; - } - } - else - { - if (type is ClassModel classModel) - { - classModel.Documentation.AddRange(GetDocumentation(rootElement)); - } + if (classModel.XmlSchemaName?.IsEmpty == false) + SetType(classModel.RootElement, classModel.XmlSchemaName, originalClassModel); - type.RootElement = rootElement; - type.RootElementName = rootElement.QualifiedName; - } + originalClassModel.BaseClass = classModel; + ((ClassModel)originalClassModel.BaseClass).DerivedTypes.Add(originalClassModel); - if (!rootElement.SubstitutionGroup.IsEmpty) - { - if (!SubstitutionGroups.TryGetValue(rootElement.SubstitutionGroup, out var substitutes)) - { - substitutes = new HashSet(); - SubstitutionGroups.Add(rootElement.SubstitutionGroup, substitutes); - } + originalClassModel.RootElementName = type.RootElementName; - substitutes.Add(new Substitute { Element = rootElement, Type = derivedClassModel ?? type }); - } + if (classModel.RootElement.SubstitutionGroup != null + && SubstitutionGroups.TryGetValue(classModel.RootElement.SubstitutionGroup, out var substitutes)) + { + foreach (var substitute in substitutes.Where(s => s.Element == classModel.RootElement)) + substitute.Type = originalClassModel; } + + classModel.RootElementName = null; + classModel.IsAbstractRoot = true; } private IEnumerable GetSubstitutedElements(XmlQualifiedName name) @@ -389,615 +366,550 @@ private IEnumerable GetSubstitutedElements(XmlQualifiedName name) } } - private TypeModel CreateTypeModel(XmlSchemaAnnotated type, XmlQualifiedName qualifiedName) + private TypeModel CreateTypeModel(XmlQualifiedName qualifiedName, XmlSchemaAnnotated type) { var key = BuildKey(type, qualifiedName); - if (!qualifiedName.IsEmpty && Types.TryGetValue(key, out TypeModel typeModel)) - { - return typeModel; - } + if (!qualifiedName.IsEmpty && Types.TryGetValue(key, out TypeModel typeModel)) return typeModel; var source = CodeUtilities.CreateUri(type.SourceUri); var namespaceModel = CreateNamespaceModel(source, qualifiedName); var docs = GetDocumentation(type); - if (type is XmlSchemaGroup group) - { - return CreateTypeModel(source, group, namespaceModel, qualifiedName, docs); - } - else if (type is XmlSchemaAttributeGroup attributeGroup) - { - return CreateTypeModel(source, attributeGroup, namespaceModel, qualifiedName, docs); - } - else if (type is XmlSchemaComplexType complexType) - { - return CreateTypeModel(source, complexType, namespaceModel, qualifiedName, docs); - } - else if (type is XmlSchemaSimpleType simpleType) - { - return CreateTypeModel(simpleType, namespaceModel, qualifiedName, docs); - } + var typeModelBuilder = new TypeModelBuilder(this, _configuration, qualifiedName, namespaceModel, docs, source); - throw new NotSupportedException($"Cannot build declaration for {qualifiedName}"); + return typeModelBuilder.Create(type); } - private TypeModel CreateTypeModel(Uri source, XmlSchemaGroup group, NamespaceModel namespaceModel, XmlQualifiedName qualifiedName, List docs) + private class TypeModelBuilder { - var name = "I" + _configuration.NamingProvider.GroupTypeNameFromQualifiedName(qualifiedName, group); - if (namespaceModel != null) { name = namespaceModel.GetUniqueTypeName(name); } + private readonly ModelBuilder builder; + private readonly GeneratorConfiguration _configuration; + private readonly XmlQualifiedName qualifiedName; + private readonly NamespaceModel namespaceModel; + private readonly List docs; + private readonly Uri source; + + public TypeModelBuilder(ModelBuilder builder, GeneratorConfiguration configuration, XmlQualifiedName qualifiedName, NamespaceModel namespaceModel, List docs, Uri source) + { + this.builder = builder; + _configuration = configuration; + this.qualifiedName = qualifiedName; + this.namespaceModel = namespaceModel; + this.docs = docs; + this.source = source; + } - var interfaceModel = new InterfaceModel(_configuration) + internal TypeModel Create(XmlSchemaAnnotated type) => type switch { - Name = name, - Namespace = namespaceModel, - XmlSchemaName = qualifiedName + XmlSchemaGroup group => CreateTypeModel(group), + XmlSchemaAttributeGroup attributeGroup => CreateTypeModel(attributeGroup), + XmlSchemaComplexType complexType => CreateTypeModel(complexType), + XmlSchemaSimpleType simpleType => CreateTypeModel(simpleType), + _ => throw new NotSupportedException($"Cannot build declaration for {qualifiedName}"), }; - interfaceModel.Documentation.AddRange(docs); - - if (namespaceModel != null) { namespaceModel.Types[name] = interfaceModel; } - - if (!qualifiedName.IsEmpty) + private InterfaceModel CreateInterfaceModel(XmlSchemaAnnotated group, string name) { - var key = BuildKey(group, qualifiedName); - Types[key] = interfaceModel; - } - - var xmlParticle = group.Particle; - var particle = new Particle(xmlParticle, group.Parent); - var items = GetElements(xmlParticle); - var properties = CreatePropertiesForElements(source, interfaceModel, particle, items.Where(i => i.XmlParticle is not XmlSchemaGroupRef)); - interfaceModel.Properties.AddRange(properties); - var interfaces = items.Select(i => i.XmlParticle).OfType() - .Select(i => (InterfaceModel)CreateTypeModel(Groups[i.RefName], i.RefName)); - interfaceModel.AddInterfaces(interfaces); + if (namespaceModel != null) + name = namespaceModel.GetUniqueTypeName(name); - return interfaceModel; - } - - private TypeModel CreateTypeModel(Uri source, XmlSchemaAttributeGroup attributeGroup, NamespaceModel namespaceModel, XmlQualifiedName qualifiedName, List docs) - { - var name = "I" + _configuration.NamingProvider.AttributeGroupTypeNameFromQualifiedName(qualifiedName, attributeGroup); - if (namespaceModel != null) { name = namespaceModel.GetUniqueTypeName(name); } + var interfaceModel = new InterfaceModel(_configuration) + { + Name = name, + Namespace = namespaceModel, + XmlSchemaName = qualifiedName + }; - var interfaceModel = new InterfaceModel(_configuration) - { - Name = name, - Namespace = namespaceModel, - XmlSchemaName = qualifiedName - }; + interfaceModel.Documentation.AddRange(docs); - interfaceModel.Documentation.AddRange(docs); + if (namespaceModel != null) + namespaceModel.Types[name] = interfaceModel; - if (namespaceModel != null) { namespaceModel.Types[name] = interfaceModel; } + if (!qualifiedName.IsEmpty) + builder.SetType(group, qualifiedName, interfaceModel); + return interfaceModel; + } - if (!qualifiedName.IsEmpty) + private TypeModel CreateTypeModel(XmlSchemaGroup group) { - var key = BuildKey(attributeGroup, qualifiedName); - Types[key] = interfaceModel; - } + var name = "I" + _configuration.NamingProvider.GroupTypeNameFromQualifiedName(qualifiedName, group); - var items = attributeGroup.Attributes; - var properties = CreatePropertiesForAttributes(source, interfaceModel, items.OfType()); - interfaceModel.Properties.AddRange(properties); - var interfaces = items.OfType() - .Select(a => (InterfaceModel)CreateTypeModel(AttributeGroups[a.RefName], a.RefName)); - interfaceModel.AddInterfaces(interfaces); + InterfaceModel interfaceModel = CreateInterfaceModel(group, name); - return interfaceModel; - } + var xmlParticle = group.Particle; + var particle = new Particle(xmlParticle, group.Parent); + var items = builder.GetElements(xmlParticle); + var properties = builder.CreatePropertiesForElements(source, interfaceModel, particle, items.Where(i => i.XmlParticle is not XmlSchemaGroupRef)); + interfaceModel.Properties.AddRange(properties); + AddInterfaces(interfaceModel, items); - private TypeModel CreateTypeModel(Uri source, XmlSchemaComplexType complexType, NamespaceModel namespaceModel, XmlQualifiedName qualifiedName, List docs) - { - var name = _configuration.NamingProvider.ComplexTypeNameFromQualifiedName(qualifiedName, complexType); - if (namespaceModel != null) - { - name = namespaceModel.GetUniqueTypeName(name); + return interfaceModel; } - var classModel = new ClassModel(_configuration) + private TypeModel CreateTypeModel(XmlSchemaAttributeGroup group) { - Name = name, - Namespace = namespaceModel, - XmlSchemaName = qualifiedName, - XmlSchemaType = complexType, - IsAbstract = complexType.IsAbstract, - IsAnonymous = string.IsNullOrEmpty(complexType.QualifiedName.Name), - IsMixed = complexType.IsMixed, - IsSubstitution = complexType.Parent is XmlSchemaElement parent && !parent.SubstitutionGroup.IsEmpty - }; + var name = "I" + _configuration.NamingProvider.AttributeGroupTypeNameFromQualifiedName(qualifiedName, group); - classModel.Documentation.AddRange(docs); + InterfaceModel interfaceModel = CreateInterfaceModel(group, name); - if (namespaceModel != null) - { - namespaceModel.Types[classModel.Name] = classModel; - } + var attributes = group.Attributes; + var properties = builder.CreatePropertiesForAttributes(source, interfaceModel, attributes.OfType()); + interfaceModel.Properties.AddRange(properties); + AddInterfaces(interfaceModel, attributes); - if (!qualifiedName.IsEmpty) - { - var key = BuildKey(complexType, qualifiedName); - Types[key] = classModel; + return interfaceModel; } - if (complexType.BaseXmlSchemaType != null && complexType.BaseXmlSchemaType.QualifiedName != AnyType) + private TypeModel CreateTypeModel(XmlSchemaComplexType complexType) { - var baseModel = CreateTypeModel(complexType.BaseXmlSchemaType, complexType.BaseXmlSchemaType.QualifiedName); - classModel.BaseClass = baseModel; - if (baseModel is ClassModel baseClassModel) { baseClassModel.DerivedTypes.Add(classModel); } - } + var name = _configuration.NamingProvider.ComplexTypeNameFromQualifiedName(qualifiedName, complexType); + if (namespaceModel != null) + name = namespaceModel.GetUniqueTypeName(name); - XmlSchemaParticle xmlParticle = null; - if (classModel.BaseClass != null) - { - if (complexType.ContentModel.Content is XmlSchemaComplexContentExtension complexContent) + var classModel = new ClassModel(_configuration) { - xmlParticle = complexContent.Particle; - } - - // If it's a restriction, do not duplicate elements on the derived class, they're already in the base class. - // See https://msdn.microsoft.com/en-us/library/f3z3wh0y.aspx - } - else xmlParticle = complexType.Particle ?? complexType.ContentTypeParticle; + Name = name, + Namespace = namespaceModel, + XmlSchemaName = qualifiedName, + XmlSchemaType = complexType, + IsAbstract = complexType.IsAbstract, + IsAnonymous = string.IsNullOrEmpty(complexType.QualifiedName.Name), + IsMixed = complexType.IsMixed, + IsSubstitution = complexType.Parent is XmlSchemaElement parent && !parent.SubstitutionGroup.IsEmpty + }; - var items = GetElements(xmlParticle, complexType).ToList(); + classModel.Documentation.AddRange(docs); - if (_configuration.GenerateInterfaces) - { - var interfaces = items.Select(i => i.XmlParticle).OfType() - .Select(i => (InterfaceModel)CreateTypeModel(Groups[i.RefName], i.RefName)).ToList(); + if (namespaceModel != null) + namespaceModel.Types[classModel.Name] = classModel; - classModel.AddInterfaces(interfaces); - } + if (!qualifiedName.IsEmpty) + builder.SetType(complexType, qualifiedName, classModel); - var particle = new Particle(xmlParticle, xmlParticle?.Parent); - var properties = CreatePropertiesForElements(source, classModel, particle, items); - classModel.Properties.AddRange(properties); + if (complexType.BaseXmlSchemaType != null && complexType.BaseXmlSchemaType.QualifiedName != AnyType) + { + var baseModel = builder.CreateTypeModel(complexType.BaseXmlSchemaType.QualifiedName, complexType.BaseXmlSchemaType); + classModel.BaseClass = baseModel; + if (baseModel is ClassModel baseClassModel) + baseClassModel.DerivedTypes.Add(classModel); + } - XmlSchemaObjectCollection attributes = null; - if (classModel.BaseClass != null) - { - if (complexType.ContentModel.Content is XmlSchemaComplexContentExtension complexContent) + XmlSchemaParticle xmlParticle = null; + if (classModel.BaseClass != null) { - attributes = complexContent.Attributes; + if (complexType.ContentModel.Content is XmlSchemaComplexContentExtension complexContent) + xmlParticle = complexContent.Particle; + + // If it's a restriction, do not duplicate elements on the derived class, they're already in the base class. + // See https://msdn.microsoft.com/en-us/library/f3z3wh0y.aspx } - else if (complexType.ContentModel.Content is XmlSchemaSimpleContentExtension simpleContent) + else { - attributes = simpleContent.Attributes; + xmlParticle = complexType.Particle ?? complexType.ContentTypeParticle; } - // If it's a restriction, do not duplicate attributes on the derived class, they're already in the base class. - // See https://msdn.microsoft.com/en-us/library/f3z3wh0y.aspx - } - else - { - attributes = complexType.Attributes; + var items = builder.GetElements(xmlParticle, complexType).ToList(); + + if (_configuration.GenerateInterfaces) + AddInterfaces(classModel, items); + + var particle = new Particle(xmlParticle, xmlParticle?.Parent); + var properties = builder.CreatePropertiesForElements(source, classModel, particle, items); + classModel.Properties.AddRange(properties); - if (attributes.Count == 0 && complexType.ContentModel != null) + XmlSchemaObjectCollection attributes = null; + if (classModel.BaseClass != null) { - var content = complexType.ContentModel.Content; + if (complexType.ContentModel.Content is XmlSchemaComplexContentExtension complexContent) + attributes = complexContent.Attributes; + else if (complexType.ContentModel.Content is XmlSchemaSimpleContentExtension simpleContent) + attributes = simpleContent.Attributes; - if (content is XmlSchemaComplexContentExtension extension) - attributes = extension.Attributes; - else if (content is XmlSchemaComplexContentRestriction restriction) - attributes = restriction.Attributes; + // If it's a restriction, do not duplicate attributes on the derived class, they're already in the base class. + // See https://msdn.microsoft.com/en-us/library/f3z3wh0y.aspx } - } + else + { + attributes = complexType.Attributes; - if (attributes != null) - { - var attributeProperties = CreatePropertiesForAttributes(source, classModel, attributes.Cast()); - classModel.Properties.AddRange(attributeProperties); + if (attributes.Count == 0 && complexType.ContentModel != null) + { + var content = complexType.ContentModel.Content; - if (_configuration.GenerateInterfaces) - { - var attributeInterfaces = attributes.OfType() - .Select(i => (InterfaceModel)CreateTypeModel(AttributeGroups[i.RefName], i.RefName)); - classModel.AddInterfaces(attributeInterfaces); + if (content is XmlSchemaComplexContentExtension extension) + attributes = extension.Attributes; + else if (content is XmlSchemaComplexContentRestriction restriction) + attributes = restriction.Attributes; + } } - } - XmlSchemaAnyAttribute anyAttribute = null; - if (complexType.AnyAttribute != null) - anyAttribute = complexType.AnyAttribute; - else if (complexType.AttributeWildcard != null) - { - var hasAnyAttribute = true; - for (var baseType = complexType.BaseXmlSchemaType; baseType != null; baseType = baseType.BaseXmlSchemaType) + if (attributes != null) { - if (baseType is not XmlSchemaComplexType baseComplexType) - continue; + var attributeProperties = builder.CreatePropertiesForAttributes(source, classModel, attributes.Cast()); + classModel.Properties.AddRange(attributeProperties); - if (baseComplexType.AttributeWildcard != null) + if (_configuration.GenerateInterfaces) + AddInterfaces(classModel, items); + } + + XmlSchemaAnyAttribute anyAttribute = null; + if (complexType.AnyAttribute != null) + { + anyAttribute = complexType.AnyAttribute; + } + else if (complexType.AttributeWildcard != null) + { + var hasAnyAttribute = true; + for (var baseType = complexType.BaseXmlSchemaType; baseType != null; baseType = baseType.BaseXmlSchemaType) { - hasAnyAttribute = false; - break; + if (baseType is not XmlSchemaComplexType baseComplexType) + continue; + + if (baseComplexType.AttributeWildcard != null) + { + hasAnyAttribute = false; + break; + } } + + if (hasAnyAttribute) + anyAttribute = complexType.AttributeWildcard; + } + + if (anyAttribute != null) + { + SimpleModel type = new(_configuration) { ValueType = typeof(XmlAttribute), UseDataTypeAttribute = false }; + var property = new PropertyModel(_configuration, "AnyAttribute", type, classModel) + { + IsAttribute = true, + IsCollection = true, + IsAny = true + }; + + var attributeDocs = GetDocumentation(anyAttribute); + property.Documentation.AddRange(attributeDocs); + + classModel.Properties.Add(property); } - if (hasAnyAttribute) - anyAttribute = complexType.AttributeWildcard; + return classModel; } - if (anyAttribute != null) + private TypeModel CreateTypeModel(XmlSchemaSimpleType simpleType) { - var property = new PropertyModel(_configuration) + List restrictions = null; + List> baseFacets = null; + + var facets = simpleType.Content switch { - OwningType = classModel, - Name = "AnyAttribute", - Type = new SimpleModel(_configuration) { ValueType = typeof(XmlAttribute), UseDataTypeAttribute = false }, - IsAttribute = true, - IsCollection = true, - IsAny = true + XmlSchemaSimpleTypeRestriction typeRestriction => typeRestriction.Facets.Cast().ToList(), + XmlSchemaSimpleTypeUnion typeUnion when AllMembersHaveFacets(typeUnion, out baseFacets) => baseFacets.SelectMany(f => f).ToList(), + _ => new(), }; - var attributeDocs = GetDocumentation(anyAttribute); - property.Documentation.AddRange(attributeDocs); + if (facets.Count > 0) + { + var enumFacets = facets.OfType().ToList(); - classModel.Properties.Add(property); - } + // If a union has enum restrictions, there must be an enum restriction in all parts of the union + // If there are other restrictions mixed into the enumeration values, we'll generate a string to play it safe. + if (enumFacets.Count > 0 && (baseFacets is null || baseFacets.All(fs => fs.OfType().Any()))) + return CreateEnumModel(simpleType, enumFacets); - return classModel; - } + restrictions = GetRestrictions(facets, simpleType).Where(r => r != null).Sanitize().ToList(); + } - private TypeModel CreateTypeModel(XmlSchemaSimpleType simpleType, NamespaceModel namespaceModel, XmlQualifiedName qualifiedName, List docs) - { - var restrictions = new List(); - var allBasesHaveEnums = true; - List facets = new(); + return CreateSimpleModel(simpleType, restrictions ?? new()); - if (simpleType.Content is XmlSchemaSimpleTypeRestriction typeRestriction) - { - facets = typeRestriction.Facets.Cast().ToList(); - } - else if (simpleType.Content is XmlSchemaSimpleTypeUnion typeUnion - && typeUnion.BaseMemberTypes.All(b => b.Content is XmlSchemaSimpleTypeRestriction r && r.Facets.Count > 0)) - { - var baseFacets = typeUnion.BaseMemberTypes.Select(b => ((XmlSchemaSimpleTypeRestriction)b.Content).Facets.Cast()).ToList(); - // if a union has enum restrictions, there must be an enum restriction in all parts of the union - allBasesHaveEnums = baseFacets.All(fs => fs.OfType().Any()); - facets = baseFacets.SelectMany(f => f).ToList(); + static bool AllMembersHaveFacets(XmlSchemaSimpleTypeUnion typeUnion, out List> baseFacets) + { + var members = typeUnion.BaseMemberTypes.Select(b => b.Content as XmlSchemaSimpleTypeRestriction); + var retval = members.All(r => r?.Facets.Count > 0); + baseFacets = !retval ? null : members.Select(r => r.Facets.Cast()).ToList(); + return retval; + } } - if (facets.Any()) + private IEnumerable GetRestrictions(IEnumerable facets, XmlSchemaSimpleType type) { - var enumFacets = facets.OfType().ToList(); - // If there are other restrictions mixed into the enumeration values, we'll generate a string to play it safe. - var isEnum = enumFacets.Any() && allBasesHaveEnums; + var min = facets.OfType().Select(f => int.Parse(f.Value)).DefaultIfEmpty().Max(); + var max = facets.OfType().Select(f => int.Parse(f.Value)).DefaultIfEmpty().Min(); - if (isEnum) + if (_configuration.DataAnnotationMode == DataAnnotationMode.All) + { + if (min > 0) yield return new MinLengthRestrictionModel(_configuration) { Value = min }; + if (max > 0) yield return new MaxLengthRestrictionModel(_configuration) { Value = max }; + } + else if (min > 0 || max > 0) { - // we got an enum - var name = _configuration.NamingProvider.EnumTypeNameFromQualifiedName(qualifiedName, simpleType); - if (namespaceModel != null) { name = namespaceModel.GetUniqueTypeName(name); } + yield return new MinMaxLengthRestrictionModel(_configuration) { Min = min, Max = max }; + } - var enumModel = new EnumModel(_configuration) + foreach (var facet in facets) + { + var valueType = type.Datatype.ValueType; + switch (facet) { - Name = name, - Namespace = namespaceModel, - XmlSchemaName = qualifiedName, - XmlSchemaType = simpleType, - IsAnonymous = string.IsNullOrEmpty(simpleType.QualifiedName.Name), - }; + case XmlSchemaLengthFacet: + var value = int.Parse(facet.Value); + if (_configuration.DataAnnotationMode == DataAnnotationMode.All) + { + yield return new MinLengthRestrictionModel(_configuration) { Value = value }; + yield return new MaxLengthRestrictionModel(_configuration) { Value = value }; + } + else + { + yield return new MinMaxLengthRestrictionModel(_configuration) { Min = value, Max = value }; + } + break; + case XmlSchemaTotalDigitsFacet: + yield return new TotalDigitsRestrictionModel(_configuration) { Value = int.Parse(facet.Value) }; break; + case XmlSchemaFractionDigitsFacet: + yield return new FractionDigitsRestrictionModel(_configuration) { Value = int.Parse(facet.Value) }; break; + case XmlSchemaPatternFacet: + yield return new PatternRestrictionModel(_configuration) { Value = facet.Value }; break; + case XmlSchemaMinInclusiveFacet: + yield return new MinInclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; break; + case XmlSchemaMinExclusiveFacet: + yield return new MinExclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; break; + case XmlSchemaMaxInclusiveFacet: + yield return new MaxInclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; break; + case XmlSchemaMaxExclusiveFacet: + yield return new MaxExclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; break; + } + } + } - enumModel.Documentation.AddRange(docs); + private static List EnsureEnumValuesUnique(List enumModelValues) + { + var enumValueGroups = from enumValue in enumModelValues + group enumValue by enumValue.Name; - foreach (var facet in enumFacets.DistinctBy(f => f.Value)) - { - var value = new EnumValueModel - { - Name = _configuration.NamingProvider.EnumMemberNameFromValue(enumModel.Name, facet.Value, facet), - Value = facet.Value - }; + foreach (var g in enumValueGroups) + { + var i = 1; + foreach (var t in g.Skip(1)) + t.Name = $"{t.Name}{i++}"; + } + + return enumModelValues; + } - var valueDocs = GetDocumentation(facet); - value.Documentation.AddRange(valueDocs); + private EnumModel CreateEnumModel(XmlSchemaSimpleType simpleType, List enumFacets) + { + // we got an enum + var name = _configuration.NamingProvider.EnumTypeNameFromQualifiedName(qualifiedName, simpleType); + if (namespaceModel != null) + name = namespaceModel.GetUniqueTypeName(name); - var deprecated = facet.Annotation != null && facet.Annotation.Items.OfType() - .Any(a => a.Markup.Any(m => m.Name == "annox:annotate" && m.HasChildNodes && m.FirstChild.Name == "jl:Deprecated")); - value.IsDeprecated = deprecated; + var enumModel = new EnumModel(_configuration) + { + Name = name, + Namespace = namespaceModel, + XmlSchemaName = qualifiedName, + XmlSchemaType = simpleType, + IsAnonymous = string.IsNullOrEmpty(simpleType.QualifiedName.Name), + }; - enumModel.Values.Add(value); - } + enumModel.Documentation.AddRange(docs); - enumModel.Values = EnsureEnumValuesUnique(enumModel.Values); - if (namespaceModel != null) + foreach (var facet in enumFacets.DistinctBy(f => f.Value)) + { + var value = new EnumValueModel { - namespaceModel.Types[enumModel.Name] = enumModel; - } + Name = _configuration.NamingProvider.EnumMemberNameFromValue(enumModel.Name, facet.Value, facet), + Value = facet.Value + }; - if (!qualifiedName.IsEmpty) - { - var key = BuildKey(simpleType, qualifiedName); - Types[key] = enumModel; - } + var valueDocs = GetDocumentation(facet); + value.Documentation.AddRange(valueDocs); - return enumModel; + value.IsDeprecated = facet.Annotation?.Items.OfType() + .Any(a => a.Markup.Any(m => m.Name == "annox:annotate" && m.HasChildNodes && m.FirstChild.Name == "jl:Deprecated")) == true; + + enumModel.Values.Add(value); } - restrictions = GetRestrictions(facets, simpleType).Where(r => r != null).Sanitize().ToList(); - } + enumModel.Values = EnsureEnumValuesUnique(enumModel.Values); + if (namespaceModel != null) + namespaceModel.Types[enumModel.Name] = enumModel; - var simpleModelName = _configuration.NamingProvider.SimpleTypeNameFromQualifiedName(qualifiedName, simpleType); - if (namespaceModel != null) { simpleModelName = namespaceModel.GetUniqueTypeName(simpleModelName); } + if (!qualifiedName.IsEmpty) + builder.SetType(simpleType, qualifiedName, enumModel); - var simpleModel = new SimpleModel(_configuration) + return enumModel; + } + + private SimpleModel CreateSimpleModel(XmlSchemaSimpleType simpleType, List restrictions) { - Name = simpleModelName, - Namespace = namespaceModel, - XmlSchemaName = qualifiedName, - XmlSchemaType = simpleType, - ValueType = simpleType.Datatype.GetEffectiveType(_configuration, restrictions), - }; + var simpleModelName = _configuration.NamingProvider.SimpleTypeNameFromQualifiedName(qualifiedName, simpleType); + if (namespaceModel != null) + simpleModelName = namespaceModel.GetUniqueTypeName(simpleModelName); + + var simpleModel = new SimpleModel(_configuration) + { + Name = simpleModelName, + Namespace = namespaceModel, + XmlSchemaName = qualifiedName, + XmlSchemaType = simpleType, + ValueType = simpleType.Datatype.GetEffectiveType(_configuration, restrictions), + }; - simpleModel.Documentation.AddRange(docs); - simpleModel.Restrictions.AddRange(restrictions); + simpleModel.Documentation.AddRange(docs); + simpleModel.Restrictions.AddRange(restrictions); - if (namespaceModel != null) - { - namespaceModel.Types[simpleModel.Name] = simpleModel; + if (namespaceModel != null) + namespaceModel.Types[simpleModel.Name] = simpleModel; + + if (!qualifiedName.IsEmpty) + builder.SetType(simpleType, qualifiedName, simpleModel); + return simpleModel; } - if (!qualifiedName.IsEmpty) + private void AddInterfaces(ReferenceTypeModel refTypeModel, IEnumerable items) { - var key = BuildKey(simpleType, qualifiedName); - Types[key] = simpleModel; + var interfaces = items.Select(i => i.XmlParticle).OfType() + .Select(i => (InterfaceModel)builder.CreateTypeModel(i.RefName, builder.Groups[i.RefName])); + refTypeModel.AddInterfaces(interfaces); } - return simpleModel; - } - - private static List EnsureEnumValuesUnique(List enumModelValues) - { - var enumValueGroups = from enumValue in enumModelValues - group enumValue by enumValue.Name; - - foreach (var g in enumValueGroups) + private void AddInterfaces(ReferenceTypeModel refTypeModel, XmlSchemaObjectCollection attributes) { - var i = 1; - foreach (var t in g.Skip(1)) - { - t.Name = $"{t.Name}{i++}"; - } + var interfaces = attributes.OfType() + .Select(a => (InterfaceModel)builder.CreateTypeModel(a.RefName, builder.AttributeGroups[a.RefName])); + refTypeModel.AddInterfaces(interfaces); } - - return enumModelValues; } - private IEnumerable CreatePropertiesForAttributes(Uri source, TypeModel typeModel, IEnumerable items) + private IEnumerable CreatePropertiesForAttributes(Uri source, TypeModel owningTypeModel, IEnumerable items) { var properties = new List(); foreach (var item in items) { - if (item is XmlSchemaAttribute attribute) + switch (item) { - if (attribute.Use != XmlSchemaUse.Prohibited) - { - var attributeQualifiedName = attribute.AttributeSchemaType.QualifiedName; - var attributeName = _configuration.NamingProvider.AttributeNameFromQualifiedName(attribute.QualifiedName, attribute); - var originalAttributeName = attributeName; - - if (attribute.Parent is XmlSchemaAttributeGroup attributeGroup - && attributeGroup.QualifiedName != typeModel.XmlSchemaName - && Types.TryGetValue(BuildKey(attributeGroup, attributeGroup.QualifiedName), out var typeModelValue) - && typeModelValue is InterfaceModel interfaceTypeModel) - { - var interfaceProperty = interfaceTypeModel.Properties.Single(p => p.XmlSchemaName == attribute.QualifiedName); - attributeQualifiedName = interfaceProperty.Type.XmlSchemaName; - attributeName = interfaceProperty.Name; - } - else - { - if (attributeQualifiedName.IsEmpty) - { - attributeQualifiedName = attribute.QualifiedName; - - if (attributeQualifiedName.IsEmpty || string.IsNullOrEmpty(attributeQualifiedName.Namespace)) - { - // inner type, have to generate a type name - var typeName = _configuration.NamingProvider.PropertyNameFromAttribute(typeModel.Name, attribute.QualifiedName.Name, attribute); - attributeQualifiedName = new XmlQualifiedName(typeName, typeModel.XmlSchemaName.Namespace); - // try to avoid name clashes - if (NameExists(attributeQualifiedName)) - { - attributeQualifiedName = new[] { "Item", "Property", "Element" } - .Select(s => new XmlQualifiedName(attributeQualifiedName.Name + s, attributeQualifiedName.Namespace)) - .First(n => !NameExists(n)); - } - } - } - - if (attributeName == typeModel.Name) - { - attributeName += "Property"; // member names cannot be the same as their enclosing type - } - } + case XmlSchemaAttribute attribute when attribute.Use != XmlSchemaUse.Prohibited: + properties.Add(PropertyFromAttribute(owningTypeModel, attribute)); + break; + case XmlSchemaAttributeGroupRef attributeGroupRef: + if (_configuration.GenerateInterfaces) + CreateTypeModel(attributeGroupRef.RefName, AttributeGroups[attributeGroupRef.RefName]); - attributeName = typeModel.GetUniquePropertyName(attributeName); + var attributeGroup = AttributeGroups[attributeGroupRef.RefName]; + var attributes = attributeGroup.Attributes.Cast() + .Where(a => !(a is XmlSchemaAttributeGroupRef agr && agr.RefName == attributeGroupRef.RefName)) + .ToList(); - var property = new PropertyModel(_configuration) + if (attributeGroup.RedefinedAttributeGroup != null) { - OwningType = typeModel, - Name = attributeName, - OriginalPropertyName = originalAttributeName, - XmlSchemaName = attribute.QualifiedName, - Type = CreateTypeModel(attribute.AttributeSchemaType, attributeQualifiedName), - IsAttribute = true, - IsNullable = attribute.Use != XmlSchemaUse.Required, - DefaultValue = attribute.DefaultValue ?? (attribute.Use != XmlSchemaUse.Optional ? attribute.FixedValue : null), - FixedValue = attribute.FixedValue, - XmlNamespace = !string.IsNullOrEmpty(attribute.QualifiedName.Namespace) && attribute.QualifiedName.Namespace != typeModel.XmlSchemaName.Namespace ? attribute.QualifiedName.Namespace : null, - }; + foreach (var attr in attributeGroup.RedefinedAttributeGroup.Attributes.Cast()) + { + var n = attr.GetQualifiedName(); - if (attribute.Form == XmlSchemaForm.None) - { - if (attribute.RefName != null && !attribute.RefName.IsEmpty) - property.Form = XmlSchemaForm.Qualified; - else - property.Form = attribute.GetSchema().AttributeFormDefault; - } - else - property.Form = attribute.Form; + if (n != null) + attributes.RemoveAll(a => a.GetQualifiedName() == n); - var attributeDocs = GetDocumentation(attribute); - property.Documentation.AddRange(attributeDocs); + attributes.Add(attr); + } + } - properties.Add(property); - } + properties.AddRange(CreatePropertiesForAttributes(source, owningTypeModel, attributes)); + break; } - else if (item is XmlSchemaAttributeGroupRef attributeGroupRef) + } + return properties; + } + + private PropertyModel PropertyFromAttribute(TypeModel owningTypeModel, XmlSchemaAttributeEx attribute) + { + var attributeQualifiedName = attribute.AttributeSchemaType.QualifiedName; + var name = _configuration.NamingProvider.AttributeNameFromQualifiedName(attribute.QualifiedName, attribute); + var originalName = name; + + if (attribute.Base.Parent is XmlSchemaAttributeGroup attributeGroup + && attributeGroup.QualifiedName != owningTypeModel.XmlSchemaName + && Types.TryGetValue(BuildKey(attributeGroup, attributeGroup.QualifiedName), out var typeModelValue) + && typeModelValue is InterfaceModel interfaceTypeModel) + { + var interfaceProperty = interfaceTypeModel.Properties.Single(p => p.XmlSchemaName == attribute.QualifiedName); + attributeQualifiedName = interfaceProperty.Type.XmlSchemaName; + name = interfaceProperty.Name; + } + else + { + if (attributeQualifiedName.IsEmpty) { - if (_configuration.GenerateInterfaces) + attributeQualifiedName = attribute.QualifiedName; + + if (attributeQualifiedName.IsEmpty || string.IsNullOrEmpty(attributeQualifiedName.Namespace)) { - CreateTypeModel(AttributeGroups[attributeGroupRef.RefName], attributeGroupRef.RefName); + // inner type, have to generate a type name + var typeName = _configuration.NamingProvider.PropertyNameFromAttribute(owningTypeModel.Name, attribute.QualifiedName.Name, attribute); + attributeQualifiedName = new XmlQualifiedName(typeName, owningTypeModel.XmlSchemaName.Namespace); + // try to avoid name clashes + if (NameExists(attributeQualifiedName)) + attributeQualifiedName = new[] { "Item", "Property", "Element" }.Select(s => new XmlQualifiedName(attributeQualifiedName.Name + s, attributeQualifiedName.Namespace)).First(n => !NameExists(n)); } + } - var attributeGroup = AttributeGroups[attributeGroupRef.RefName]; - var attributes = attributeGroup.Attributes.Cast() - .Where(a => !(a is XmlSchemaAttributeGroupRef agr && agr.RefName == attributeGroupRef.RefName)) - .ToList(); - - if (attributeGroup.RedefinedAttributeGroup != null) - { - foreach (var attr in attributeGroup.RedefinedAttributeGroup.Attributes.Cast()) - { - var n = attr.GetQualifiedName(); + if (name == owningTypeModel.Name) + name += "Property"; + } - if (n != null) - { - attributes.RemoveAll(a => a.GetQualifiedName() == n); - } + name = owningTypeModel.GetUniquePropertyName(name); - attributes.Add(attr); - } - } + var typeModel = CreateTypeModel(attributeQualifiedName, attribute.AttributeSchemaType); + var property = new PropertyModel(_configuration, name, typeModel, owningTypeModel) + { + IsAttribute = true, + IsRequired = attribute.Use == XmlSchemaUse.Required + }; - var groupProperties = CreatePropertiesForAttributes(source, typeModel, attributes); - properties.AddRange(groupProperties); - } - } + property.SetFromNode(originalName, attribute.Use != XmlSchemaUse.Optional, attribute); + property.SetSchemaNameAndNamespace(owningTypeModel, attribute); + property.Documentation.AddRange(GetDocumentation(attribute)); - return properties; + return property; } - private IEnumerable CreatePropertiesForElements(Uri source, TypeModel typeModel, Particle particle, IEnumerable items, + private IEnumerable CreatePropertiesForElements(Uri source, TypeModel owningTypeModel, Particle particle, IEnumerable items, Substitute substitute = null, int order = 0) { var properties = new List(); - var xmlParticle = particle.XmlParticle; foreach (var item in items) { PropertyModel property = null; - // ElementSchemaType must be non-null. This is not the case when maxOccurs="0". - if (item.XmlParticle is XmlSchemaElement element && element.ElementSchemaType != null) + switch (item.XmlParticle) { - var elementQualifiedName = element.ElementSchemaType.QualifiedName; - - if (elementQualifiedName.IsEmpty) - { - elementQualifiedName = element.RefName; - - if (elementQualifiedName.IsEmpty) - { - // inner type, have to generate a type name - var typeModelName = xmlParticle is XmlSchemaGroupRef groupRef ? groupRef.RefName : typeModel.XmlSchemaName; - var typeName = _configuration.NamingProvider.PropertyNameFromElement(typeModelName.Name, element.QualifiedName.Name, element); - elementQualifiedName = new XmlQualifiedName(typeName, typeModel.XmlSchemaName.Namespace); - // try to avoid name clashes - if (NameExists(elementQualifiedName)) - { - elementQualifiedName = new[] { "Item", "Property", "Element" } - .Select(s => new XmlQualifiedName(elementQualifiedName.Name + s, elementQualifiedName.Namespace)) - .First(n => !NameExists(n)); - } - } - } - - var effectiveElement = substitute?.Element ?? element; - var isNullableByChoice = IsNullableByChoice(item.XmlParent); - var propertyName = _configuration.NamingProvider.ElementNameFromQualifiedName(effectiveElement.QualifiedName, effectiveElement); - var originalPropertyName = propertyName; - if (propertyName == typeModel.Name) - { - propertyName += "Property"; // member names cannot be the same as their enclosing type - } - - propertyName = typeModel.GetUniquePropertyName(propertyName); - - property = new PropertyModel(_configuration) - { - OwningType = typeModel, - XmlSchemaName = effectiveElement.QualifiedName, - Name = propertyName, - OriginalPropertyName = originalPropertyName, - Type = substitute?.Type ?? CreateTypeModel(element.ElementSchemaType, elementQualifiedName), - IsNillable = element.IsNillable, - IsNullable = item.MinOccurs < 1.0m || isNullableByChoice, - IsCollection = item.MaxOccurs > 1.0m || particle.MaxOccurs > 1.0m, // http://msdn.microsoft.com/en-us/library/vstudio/d3hx2s7e(v=vs.100).aspx - DefaultValue = element.DefaultValue ?? ((item.MinOccurs >= 1.0m && !isNullableByChoice) ? element.FixedValue : null), - FixedValue = element.FixedValue, - XmlNamespace = !string.IsNullOrEmpty(effectiveElement.QualifiedName.Namespace) && effectiveElement.QualifiedName.Namespace != typeModel.XmlSchemaName.Namespace - ? effectiveElement.QualifiedName.Namespace : null, - XmlParticle = item.XmlParticle, - XmlParent = item.XmlParent, - Particle = item - }; - - if (element.Form == XmlSchemaForm.None) - { - if (element.RefName != null && !element.RefName.IsEmpty) - property.Form = XmlSchemaForm.Qualified; - else - property.Form = element.GetSchema().ElementFormDefault; - } - else - property.Form = element.Form; - - if (property.IsArray && !_configuration.GenerateComplexTypesForCollections) - { - property.Type.Namespace.Types.Remove(property.Type.Name); - } - } - else - { - if (item.XmlParticle is XmlSchemaAny any) - { - property = new PropertyModel(_configuration) + // ElementSchemaType must be non-null. This is not the case when maxOccurs="0". + case XmlSchemaElement element when element.ElementSchemaType != null: + property = PropertyFromElement(owningTypeModel, element, particle, item, substitute); + break; + case XmlSchemaAny any: + SimpleModel typeModel = new(_configuration) { - OwningType = typeModel, - Name = "Any", - Type = new SimpleModel(_configuration) { ValueType = (_configuration.UseXElementForAny ? typeof(XElement) : typeof(XmlElement)), UseDataTypeAttribute = false }, - IsNullable = item.MinOccurs < 1.0m || IsNullableByChoice(item.XmlParent), - IsCollection = item.MaxOccurs > 1.0m || particle.MaxOccurs > 1.0m, // http://msdn.microsoft.com/en-us/library/vstudio/d3hx2s7e(v=vs.100).aspx - IsAny = true, - XmlParticle = item.XmlParticle, - XmlParent = item.XmlParent, - Particle = item + ValueType = _configuration.UseXElementForAny ? typeof(XElement) : typeof(XmlElement), + UseDataTypeAttribute = false }; - } - else - { - if (item.XmlParticle is XmlSchemaGroupRef groupRef) - { - var group = Groups[groupRef.RefName]; + property = new PropertyModel(_configuration, "Any", typeModel, owningTypeModel) { IsAny = true }; + property.SetFromParticles(particle, item, item.MinOccurs >= 1.0m && !IsNullableByChoice(item.XmlParent)); + break; + case XmlSchemaGroupRef groupRef: + var group = Groups[groupRef.RefName]; - if (_configuration.GenerateInterfaces) - { - CreateTypeModel(group, groupRef.RefName); - } + if (_configuration.GenerateInterfaces) + CreateTypeModel(groupRef.RefName, group); - var groupItems = GetElements(groupRef.Particle).ToList(); - var groupProperties = CreatePropertiesForElements(source, typeModel, item, groupItems, order: order).ToList(); - if (_configuration.EmitOrder) - { - order += groupProperties.Count; - } - properties.AddRange(groupProperties); - } - } + var groupItems = GetElements(groupRef.Particle).ToList(); + var groupProperties = CreatePropertiesForElements(source, owningTypeModel, item, groupItems, order: order).ToList(); + if (_configuration.EmitOrder) + order += groupProperties.Count; + + properties.AddRange(groupProperties); + break; } // Discard duplicate property names. This is most likely due to: @@ -1009,9 +921,8 @@ private IEnumerable CreatePropertiesForElements(Uri source, TypeM property.Documentation.AddRange(itemDocs); if (_configuration.EmitOrder) - { property.Order = order++; - } + property.IsDeprecated = itemDocs.Any(d => d.Text.StartsWith("DEPRECATED")); properties.Add(property); @@ -1042,6 +953,54 @@ private static bool IsNullableByChoice(XmlSchemaObject parent) return false; } + private PropertyModel PropertyFromElement(TypeModel owningTypeModel, XmlSchemaElementEx element, Particle particle, Particle item, Substitute substitute) + { + PropertyModel property; + XmlSchemaElementEx effectiveElement = substitute?.Element ?? element; + var name = _configuration.NamingProvider.ElementNameFromQualifiedName(effectiveElement.QualifiedName, effectiveElement); + var originalName = name; + if (name == owningTypeModel.Name) + name += "Property"; // member names cannot be the same as their enclosing type + + name = owningTypeModel.GetUniquePropertyName(name); + + var typeModel = substitute?.Type ?? CreateTypeModel(GetQualifiedName(owningTypeModel, particle.XmlParticle, element), element.ElementSchemaType); + + property = new PropertyModel(_configuration, name, typeModel, owningTypeModel) { IsNillable = element.IsNillable }; + var isRequired = item.MinOccurs >= 1.0m && !IsNullableByChoice(item.XmlParent); + property.SetFromParticles(particle, item, isRequired); + property.SetFromNode(originalName, isRequired, element); + property.SetSchemaNameAndNamespace(owningTypeModel, effectiveElement); + + if (property.IsArray && !_configuration.GenerateComplexTypesForCollections) + property.Type.Namespace.Types.Remove(property.Type.Name); + + return property; + } + + private XmlQualifiedName GetQualifiedName(TypeModel typeModel, XmlSchemaParticle xmlParticle, XmlSchemaElementEx element) + { + var elementQualifiedName = element.ElementSchemaType.QualifiedName; + + if (elementQualifiedName.IsEmpty) + { + elementQualifiedName = element.RefName; + + if (elementQualifiedName.IsEmpty) + { + // inner type, have to generate a type name + var typeModelName = xmlParticle is XmlSchemaGroupRef groupRef ? groupRef.RefName : typeModel.XmlSchemaName; + var typeName = _configuration.NamingProvider.PropertyNameFromElement(typeModelName.Name, element.QualifiedName.Name, element); + elementQualifiedName = new XmlQualifiedName(typeName, typeModel.XmlSchemaName.Namespace); + // try to avoid name clashes + if (NameExists(elementQualifiedName)) + elementQualifiedName = new[] { "Item", "Property", "Element" }.Select(s => new XmlQualifiedName(elementQualifiedName.Name + s, elementQualifiedName.Namespace)).First(n => !NameExists(n)); + } + } + + return elementQualifiedName; + } + private NamespaceModel CreateNamespaceModel(Uri source, XmlQualifiedName qualifiedName) { NamespaceModel namespaceModel = null; @@ -1065,72 +1024,6 @@ private bool NameExists(XmlQualifiedName name) return elements.Concat(types).Any(n => n.Namespace == name.Namespace && name.Name.Equals(n.Name, StringComparison.OrdinalIgnoreCase)); } - private IEnumerable GetRestrictions(IEnumerable facets, XmlSchemaSimpleType type) - { - var min = facets.OfType().Select(f => int.Parse(f.Value)).DefaultIfEmpty().Max(); - var max = facets.OfType().Select(f => int.Parse(f.Value)).DefaultIfEmpty().Min(); - - if (_configuration.DataAnnotationMode == DataAnnotationMode.All) - { - if (min > 0) { yield return new MinLengthRestrictionModel(_configuration) { Value = min }; } - if (max > 0) { yield return new MaxLengthRestrictionModel(_configuration) { Value = max }; } - } - else if (min > 0 || max > 0) - { - yield return new MinMaxLengthRestrictionModel(_configuration) { Min = min, Max = max }; - } - - foreach (var facet in facets) - { - if (facet is XmlSchemaLengthFacet) - { - var value = int.Parse(facet.Value); - if (_configuration.DataAnnotationMode == DataAnnotationMode.All) - { - yield return new MinLengthRestrictionModel(_configuration) { Value = value }; - yield return new MaxLengthRestrictionModel(_configuration) { Value = value }; - } - else - { - yield return new MinMaxLengthRestrictionModel(_configuration) { Min = value, Max = value }; - } - } - - if (facet is XmlSchemaTotalDigitsFacet) - { - yield return new TotalDigitsRestrictionModel(_configuration) { Value = int.Parse(facet.Value) }; - } - if (facet is XmlSchemaFractionDigitsFacet) - { - yield return new FractionDigitsRestrictionModel(_configuration) { Value = int.Parse(facet.Value) }; - } - - if (facet is XmlSchemaPatternFacet) - { - yield return new PatternRestrictionModel(_configuration) { Value = facet.Value }; - } - - var valueType = type.Datatype.ValueType; - - if (facet is XmlSchemaMinInclusiveFacet) - { - yield return new MinInclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; - } - if (facet is XmlSchemaMinExclusiveFacet) - { - yield return new MinExclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; - } - if (facet is XmlSchemaMaxInclusiveFacet) - { - yield return new MaxInclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; - } - if (facet is XmlSchemaMaxExclusiveFacet) - { - yield return new MaxExclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; - } - } - } - public IEnumerable GetElements(XmlSchemaGroupBase groupBase) { if (groupBase?.Items != null) @@ -1149,28 +1042,29 @@ public IEnumerable GetElements(XmlSchemaGroupBase groupBase) public IEnumerable GetElements(XmlSchemaObject item, XmlSchemaObject parent) { - if (item == null) { yield break; } - - if (item is XmlSchemaElement element) { yield return new Particle(element, parent); } - - if (item is XmlSchemaAny any) { yield return new Particle(any, parent); } - - if (item is XmlSchemaGroupRef groupRef) { yield return new Particle(groupRef, parent); } - - if (item is XmlSchemaGroupBase itemGroupBase) + switch (item) { - foreach (var groupBaseElement in GetElements(itemGroupBase)) - yield return groupBaseElement; + case null: + yield break; + case XmlSchemaElement element: + yield return new Particle(element, parent); break; + case XmlSchemaAny any: + yield return new Particle(any, parent); break; + case XmlSchemaGroupRef groupRef: + yield return new Particle(groupRef, parent); break; + case XmlSchemaGroupBase itemGroupBase: + foreach (var groupBaseElement in GetElements(itemGroupBase)) + yield return groupBaseElement; + break; } } public static List GetDocumentation(XmlSchemaAnnotated annotated) { - if (annotated.Annotation == null) { return new List(); } - - return annotated.Annotation.Items.OfType() - .Where(d => d.Markup != null && d.Markup.Any()) - .Select(d => new DocumentationModel { Language = d.Language, Text = new XText(d.Markup.First().InnerText).ToString() }) + return annotated.Annotation == null ? new List() + : annotated.Annotation.Items.OfType() + .Where(d => d.Markup?.Length > 0) + .Select(d => new DocumentationModel { Language = d.Language, Text = new XText(d.Markup[0].InnerText).ToString() }) .Where(d => !string.IsNullOrEmpty(d.Text)) .ToList(); } @@ -1187,12 +1081,8 @@ private string BuildNamespace(Uri source, string xmlNamespace) { var key = new NamespaceKey(source, xmlNamespace); var result = _configuration.NamespaceProvider.FindNamespace(key); - if (!string.IsNullOrEmpty(result)) - { - return result; - } - - throw new ArgumentException(string.Format("Namespace {0} not provided through map or generator.", xmlNamespace)); + return !string.IsNullOrEmpty(result) ? result + : throw new ArgumentException(string.Format("Namespace {0} not provided through map or generator.", xmlNamespace)); } } } \ No newline at end of file diff --git a/XmlSchemaClassGenerator/TypeModel.cs b/XmlSchemaClassGenerator/TypeModel.cs index 3fed4ebb..583e1fd6 100644 --- a/XmlSchemaClassGenerator/TypeModel.cs +++ b/XmlSchemaClassGenerator/TypeModel.cs @@ -149,14 +149,9 @@ public virtual CodeExpression GetDefaultValueFor(string defaultString, bool attr public class InterfaceModel : ReferenceTypeModel { - public InterfaceModel(GeneratorConfiguration configuration) - : base(configuration) - { - Properties = new List(); - DerivedTypes = new List(); - } + public InterfaceModel(GeneratorConfiguration configuration) : base(configuration) { } - public List DerivedTypes { get; set; } + public List DerivedTypes { get; } = new(); public override CodeTypeDeclaration Generate() { @@ -219,14 +214,10 @@ public class ClassModel : ReferenceTypeModel public bool IsMixed { get; set; } public bool IsSubstitution { get; set; } public TypeModel BaseClass { get; set; } - public List DerivedTypes { get; set; } + public List DerivedTypes { get; set; } = new(); public override bool IsSubtype => BaseClass != null; - public ClassModel(GeneratorConfiguration configuration) - : base(configuration) - { - DerivedTypes = new List(); - } + public ClassModel(GeneratorConfiguration configuration) : base(configuration) { } public IEnumerable AllBaseClasses { @@ -279,16 +270,13 @@ public override CodeTypeDeclaration Generate() }; classDeclaration.Members.Add(propertyChangedEvent); - var propertyChangedModel = new PropertyModel(Configuration) - { - Name = propertyChangedEvent.Name, - OwningType = this, - Type = new SimpleModel(Configuration) { ValueType = typeof(PropertyChangedEventHandler) } - }; + SimpleModel type = new(Configuration) { ValueType = typeof(PropertyChangedEventHandler) }; + var propertyChangedModel = new PropertyModel(Configuration, propertyChangedEvent.Name, type, this); Configuration.MemberVisitor(propertyChangedEvent, propertyChangedModel); - var param = new CodeParameterDeclarationExpression(typeof(string), "propertyName"); + var param = new CodeParameterDeclarationExpression(typeof(string), "propertyName = null"); + param.CustomAttributes.Add(new(TypeRef())); var threadSafeDelegateInvokeExpression = new CodeSnippetExpression($"{propertyChangedEvent.Name}?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs({param.Name}))"); var onPropChangedMethod = new CodeMemberMethod { @@ -309,27 +297,24 @@ public override CodeTypeDeclaration Generate() } else if (!string.IsNullOrEmpty(Configuration.TextValuePropertyName)) { + var textName = Configuration.TextValuePropertyName; + var enableDataBinding = Configuration.EnableDataBinding; var typeReference = BaseClass.GetReferenceFor(Namespace); - var member = new CodeMemberField(typeReference, Configuration.TextValuePropertyName) + CodeMemberField backingFieldMember = null; + if (enableDataBinding) { - Attributes = MemberAttributes.Public, - }; - - if (Configuration.EnableDataBinding) - { - var backingFieldMember = new CodeMemberField(typeReference, member.Name.ToBackingField(Configuration.PrivateMemberPrefix)) + backingFieldMember = new CodeMemberField(typeReference, textName.ToBackingField(Configuration.PrivateMemberPrefix)) { Attributes = MemberAttributes.Private }; - member.Name += PropertyModel.GetAccessors(member.Name, backingFieldMember.Name, BaseClass.GetPropertyValueTypeCode(), false); classDeclaration.Members.Add(backingFieldMember); } - else + + CodeMemberField text = new(typeReference, textName + PropertyModel.GetAccessors(backingFieldMember, enableDataBinding, BaseClass.GetPropertyValueTypeCode())) { - // hack to generate automatic property - member.Name += GetSet; - } + Attributes = MemberAttributes.Public, + }; var docs = new List { new() { Language = English, Text = "Gets or sets the text value." }, @@ -341,25 +326,20 @@ public override CodeTypeDeclaration Generate() if (BaseClass is SimpleModel simpleModel) { docs.AddRange(simpleModel.Restrictions.Select(r => new DocumentationModel { Language = English, Text = r.Description })); - member.CustomAttributes.AddRange(simpleModel.GetRestrictionAttributes().ToArray()); + text.CustomAttributes.AddRange(simpleModel.GetRestrictionAttributes().ToArray()); if (BaseClass.GetQualifiedName() is { Namespace: XmlSchema.Namespace, Name: var name } && (simpleModel.XmlSchemaType.Datatype.IsDataTypeAttributeAllowed() ?? simpleModel.UseDataTypeAttribute)) attribute.Arguments.Add(new CodeAttributeArgument(nameof(XmlTextAttribute.DataType), new CodePrimitiveExpression(name))); } - member.Comments.AddRange(GetComments(docs).ToArray()); + text.Comments.AddRange(GetComments(docs).ToArray()); - member.CustomAttributes.Add(attribute); - classDeclaration.Members.Add(member); + text.CustomAttributes.Add(attribute); + classDeclaration.Members.Add(text); - var valuePropertyModel = new PropertyModel(Configuration) - { - Name = Configuration.TextValuePropertyName, - OwningType = this, - Type = BaseClass - }; + var valuePropertyModel = new PropertyModel(Configuration, textName, BaseClass, this); - Configuration.MemberVisitor(member, valuePropertyModel); + Configuration.MemberVisitor(text, valuePropertyModel); } } @@ -376,11 +356,8 @@ public override CodeTypeDeclaration Generate() if (keyProperty == null) { - keyProperty = new PropertyModel(Configuration) + keyProperty = new PropertyModel(Configuration, "Id", new SimpleModel(Configuration) { ValueType = typeof(long) }, this) { - Name = "Id", - Type = new SimpleModel(Configuration) { ValueType = typeof(long) }, - OwningType = this, Documentation = { new() { Language = English, Text = "Gets or sets a value uniquely identifying this entity." }, new() { Language = German, Text = "Ruft einen Wert ab, der diese Entität eindeutig identifiziert, oder legt diesen fest." } @@ -415,20 +392,12 @@ public override CodeTypeDeclaration Generate() { propName = $"Text_{propertyIndex}"; } - var text = new CodeMemberField(typeof(string[]), propName); // hack to generate automatic property - text.Name += GetSet; - text.Attributes = MemberAttributes.Public; - var xmlTextAttribute = AttributeDecl(); - text.CustomAttributes.Add(xmlTextAttribute); + var text = new CodeMemberField(typeof(string[]), propName + PropertyModel.GetAccessors()) { Attributes = MemberAttributes.Public }; + text.CustomAttributes.Add(AttributeDecl()); classDeclaration.Members.Add(text); - var textPropertyModel = new PropertyModel(Configuration) - { - Name = propName, - OwningType = this, - Type = new SimpleModel(Configuration) { ValueType = typeof(string) } - }; + var textPropertyModel = new PropertyModel(Configuration, propName, new SimpleModel(Configuration) { ValueType = typeof(string) }, this); Configuration.MemberVisitor(text, textPropertyModel); } @@ -498,15 +467,10 @@ public override CodeExpression GetDefaultValueFor(string defaultString, bool att public class ReferenceTypeModel : TypeModel { - public ReferenceTypeModel(GeneratorConfiguration configuration) - : base(configuration) - { - Properties = new List(); - Interfaces = new List(); - } + public ReferenceTypeModel(GeneratorConfiguration configuration) : base(configuration) { } - public List Properties { get; set; } - public List Interfaces { get; } + public List Properties { get; } = new(); + public List Interfaces { get; } = new(); public void AddInterfaces(IEnumerable interfaces) { @@ -528,65 +492,104 @@ public class PropertyModel : GeneratorModel private const string Specified = nameof(Specified); private const string Namespace = nameof(XmlRootAttribute.Namespace); - public TypeModel OwningType { get; set; } - public string Name { get; set; } - public string OriginalPropertyName { get; set; } + // ctor + public List Documentation { get; } = new(); + public List Substitutes { get; } = new(); + public TypeModel OwningType { get; } + public TypeModel Type { get; } + public string Name { get; set; } // Only set when renaming interfaces. + + // private + public string OriginalPropertyName { get; private set; } + public string DefaultValue { get; private set; } + public string FixedValue { get; private set; } + public XmlSchemaForm Form { get; private set; } + public string XmlNamespace { get; private set; } + public XmlQualifiedName XmlSchemaName { get; private set; } + public XmlSchemaParticle XmlParticle { get; private set; } + public XmlSchemaObject XmlParent { get; private set; } + public Particle Particle { get; private set; } + + // public public bool IsAttribute { get; set; } - public TypeModel Type { get; set; } - public bool IsNullable { get; set; } + public bool IsRequired { get; set; } public bool IsNillable { get; set; } public bool IsCollection { get; set; } - public string DefaultValue { get; set; } - public string FixedValue { get; set; } - public XmlSchemaForm Form { get; set; } - public string XmlNamespace { get; set; } - public List Documentation { get; } public bool IsDeprecated { get; set; } - public XmlQualifiedName XmlSchemaName { get; set; } public bool IsAny { get; set; } public int? Order { get; set; } public bool IsKey { get; set; } - public XmlSchemaParticle XmlParticle { get; set; } - public XmlSchemaObject XmlParent { get; set; } - public Particle Particle { get; set; } - public List Substitutes { get; set; } - public PropertyModel(GeneratorConfiguration configuration) : base(configuration) + public PropertyModel(GeneratorConfiguration configuration, string name, TypeModel type, TypeModel owningType) : base(configuration) { - Documentation = new List(); - Substitutes = new List(); + Name = name; + Type = type; + OwningType = owningType; } - internal static string GetAccessors(string memberName, string backingFieldName, PropertyValueTypeCode typeCode, bool privateSetter, bool withDataBinding = true) + public void SetFromNode(string originalName, bool useFixedIfNoDefault, IXmlSchemaNode xs) { - string assign = $@" - {backingFieldName} = value;"; + OriginalPropertyName = originalName; - return CodeUtilities.NormalizeNewlines($@" + DefaultValue = xs.DefaultValue ?? (useFixedIfNoDefault ? xs.FixedValue : null); + FixedValue = xs.FixedValue; + Form = xs.Form switch + { + XmlSchemaForm.None => xs.RefName?.IsEmpty == false ? XmlSchemaForm.Qualified : xs.FormDefault, + _ => xs.Form, + }; + } + + public void SetFromParticles(Particle particle, Particle item, bool isRequired) + { + Particle = item; + XmlParticle = item.XmlParticle; + XmlParent = item.XmlParent; + + IsRequired = isRequired; + IsCollection = item.MaxOccurs > 1.0m || particle.MaxOccurs > 1.0m; // http://msdn.microsoft.com/en-us/library/vstudio/d3hx2s7e(v=vs.100).aspx + } + + public void SetSchemaNameAndNamespace(TypeModel owningTypeModel, IXmlSchemaNode xs) + { + XmlSchemaName = xs.QualifiedName; + XmlNamespace = string.IsNullOrEmpty(xs.QualifiedName.Namespace) + || xs.QualifiedName.Namespace == owningTypeModel.XmlSchemaName.Namespace ? null + : xs.QualifiedName.Namespace; + } + + internal static string GetAccessors(CodeMemberField backingField = null, bool withDataBinding = false, PropertyValueTypeCode typeCode = PropertyValueTypeCode.Other, bool privateSetter = false) + { + return backingField == null ? " { get; set; }" : CodeUtilities.NormalizeNewlines($@" {{ get {{ - return {backingFieldName}; + return {backingField.Name}; }} {(privateSetter ? "private " : string.Empty)}set {{{(typeCode, withDataBinding) switch { (PropertyValueTypeCode.ValueType, true) => $@" - if (!{backingFieldName}.Equals(value)) - {{{assign} - OnPropertyChanged(nameof({memberName})); - }}", + if ({checkEquality()}){assignAndNotify()}", (PropertyValueTypeCode.Other or PropertyValueTypeCode.Array, true) => $@" - if ({backingFieldName} == value) + if ({backingField.Name} == value) return; - if ({backingFieldName} == null || value == null || !{backingFieldName}.{(typeCode is PropertyValueTypeCode.Other ? EqualsMethod : nameof(Enumerable.SequenceEqual))}(value)) - {{{assign} - OnPropertyChanged(nameof({memberName})); - }}", - _ => assign, + if ({backingField.Name} == null || value == null || {checkEquality()}){assignAndNotify()}", + _ => assign(), }} }} }}"); + + string assign() => $@" + {backingField.Name} = value;"; + + string assignAndNotify() => $@" + {{{assign()} + {OnPropertyChanged}(); + }}"; + + string checkEquality() + => $"!{backingField.Name}.{(typeCode is PropertyValueTypeCode.Array ? nameof(Enumerable.SequenceEqual) : EqualsMethod)}(value)"; } private ClassModel TypeClassModel => Type as ClassModel; @@ -595,33 +598,31 @@ internal static string GetAccessors(string memberName, string backingFieldName, /// A property is an array if it is a sequence containing a single element with maxOccurs > 1. /// public bool IsArray => Configuration.UseArrayItemAttribute - && !IsCollection && !IsAttribute && !IsList && TypeClassModel != null + && !IsCollection && !IsList && TypeClassModel != null && TypeClassModel.BaseClass == null && TypeClassModel.Properties.Count == 1 && !TypeClassModel.Properties[0].IsAttribute && !TypeClassModel.Properties[0].IsAny && TypeClassModel.Properties[0].IsCollection; + private bool IsEnumerable => IsCollection || IsArray || IsList; + private TypeModel PropertyType => !IsArray ? Type : TypeClassModel.Properties[0].Type; - private bool IsNullableValueType => DefaultValue == null - && IsNullable && !(IsCollection || IsArray) && !IsList - && ((PropertyType is EnumModel) || (PropertyType is SimpleModel model && model.ValueType.IsValueType)); + private bool IsNullable => DefaultValue == null && !IsRequired && !IsEnumerable; - private bool IsNullableReferenceType => DefaultValue == null - && IsNullable && (IsCollection || IsArray || IsList || PropertyType is ClassModel || (PropertyType is SimpleModel model && !model.ValueType.IsValueType)); + private bool IsValueType => PropertyType is EnumModel || (PropertyType is SimpleModel model && model.ValueType.IsValueType); - private bool IsNillableValueType => IsNillable - && !(IsCollection || IsArray) - && ((PropertyType is EnumModel) || (PropertyType is SimpleModel model && model.ValueType.IsValueType)); + private bool IsNullableValueType => IsNullable && IsValueType; + + private bool IsNullableReferenceType => IsNullable && (PropertyType is ClassModel || (PropertyType is SimpleModel model && !model.ValueType.IsValueType)); + + private bool IsNillableValueType => IsNillable && !IsEnumerable && IsValueType; private bool IsList => Type.XmlSchemaType?.Datatype?.Variety == XmlSchemaDatatypeVariety.List; - private bool IsPrivateSetter => Configuration.CollectionSettersMode == CollectionSettersMode.Private - && (IsCollection || IsArray || (IsList && IsAttribute)); + private bool IsPrivateSetter => IsEnumerable && Configuration.CollectionSettersMode == CollectionSettersMode.Private; - private CodeTypeReference TypeReference => PropertyType.GetReferenceFor(OwningType.Namespace, - collection: IsCollection || IsArray || (IsList && IsAttribute), - attribute: IsAttribute); + private CodeTypeReference TypeReference => PropertyType.GetReferenceFor(OwningType.Namespace, collection: IsEnumerable, attribute: IsAttribute); private void AddDocs(CodeTypeMember member) { @@ -670,7 +671,7 @@ public void AddInterfaceMembersTo(CodeTypeDeclaration typeDeclaration) HasSet = !isPrivateSetter }; - if (DefaultValue != null && IsNullable) + if (DefaultValue != null && !IsRequired) { var defaultValueExpression = propertyType.GetDefaultValueFor(DefaultValue, IsAttribute); @@ -692,19 +693,17 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi // Note: We use CodeMemberField because CodeMemberProperty doesn't allow for private set var member = new CodeMemberField() { Name = Name }; - var typeClassModel = TypeClassModel; var isArray = IsArray; + var isEnumerable = IsEnumerable; var propertyType = PropertyType; var isNullableValueType = IsNullableValueType; - var isNullableReferenceType = IsNullableReferenceType; - var isPrivateSetter = IsPrivateSetter; var typeReference = TypeReference; CodeAttributeDeclaration ignoreAttribute = new(TypeRef()); CodeAttributeDeclaration notMappedAttribute = new(CodeUtilities.CreateTypeReference(Attributes.NotMapped, Configuration)); CodeMemberField backingField = null; - if (withDataBinding || DefaultValue != null || IsCollection || isArray) + if (withDataBinding || DefaultValue != null || isEnumerable) { backingField = IsNillableValueType ? new CodeMemberField(NullableTypeRef(typeReference), OwningType.GetUniqueFieldName(this)) @@ -713,7 +712,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi typeDeclaration.Members.Add(backingField); } - if (DefaultValue == null || ((IsCollection || isArray || (IsList && IsAttribute)) && IsNullable)) + if (DefaultValue == null || (isEnumerable && !IsRequired)) { if (isNullableValueType && Configuration.GenerateNullables && !(Configuration.UseShouldSerializePattern && !IsAttribute)) member.Name += Value; @@ -739,16 +738,8 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi member.Type = typeReference; } - if (backingField != null) - { - var propertyValueTypeCode = IsCollection || isArray ? PropertyValueTypeCode.Array : propertyType.GetPropertyValueTypeCode(); - member.Name += GetAccessors(member.Name, backingField.Name, propertyValueTypeCode, isPrivateSetter, withDataBinding); - } - else - { - var privateSetter = isPrivateSetter ? "private " : string.Empty; - member.Name += $" {{ get; {privateSetter}set; }}"; // hack to generate automatic property - } + var propertyValueTypeCode = IsCollection || isArray ? PropertyValueTypeCode.Array : propertyType.GetPropertyValueTypeCode(); + member.Name += GetAccessors(backingField, withDataBinding, propertyValueTypeCode, IsPrivateSetter); } else { @@ -757,9 +748,9 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi member.Type = IsNillableValueType ? NullableTypeRef(typeReference) : typeReference; - member.Name += GetAccessors(member.Name, backingField.Name, propertyType.GetPropertyValueTypeCode(), false, withDataBinding); + member.Name += GetAccessors(backingField, withDataBinding, propertyType.GetPropertyValueTypeCode()); - if (IsNullable && (defaultValueExpression is CodePrimitiveExpression or CodeFieldReferenceExpression) && !CodeUtilities.IsXmlLangOrSpace(XmlSchemaName)) + if (!IsRequired && (defaultValueExpression is CodePrimitiveExpression or CodeFieldReferenceExpression) && !CodeUtilities.IsXmlLangOrSpace(XmlSchemaName)) member.CustomAttributes.Add(CreateDefaultValueAttribute(typeReference, defaultValueExpression)); } @@ -768,7 +759,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi AddDocs(member); - if (!IsNullable && Configuration.DataAnnotationMode != DataAnnotationMode.None) + if (IsRequired && Configuration.DataAnnotationMode != DataAnnotationMode.None) { var requiredAttribute = new CodeAttributeDeclaration(CodeUtilities.CreateTypeReference(Attributes.Required, Configuration)); member.CustomAttributes.Add(requiredAttribute); @@ -794,7 +785,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi CodeMemberField specifiedMember = null; if (generateSpecifiedProperty) { - specifiedMember = new CodeMemberField(typeof(bool), specifiedName + Specified + GetSet); + specifiedMember = new CodeMemberField(typeof(bool), specifiedName + Specified + GetAccessors()); specifiedMember.CustomAttributes.Add(ignoreAttribute); if (Configuration.EntityFramework && generateNullablesProperty) { specifiedMember.CustomAttributes.Add(notMappedAttribute); } specifiedMember.Attributes = MemberAttributes.Public; @@ -805,7 +796,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi specifiedMember.Comments.AddRange(GetComments(specifiedDocs).ToArray()); typeDeclaration.Members.Add(specifiedMember); - var specifiedMemberPropertyModel = new PropertyModel(Configuration) { Name = specifiedName + Specified }; + var specifiedMemberPropertyModel = new PropertyModel(Configuration, specifiedName + Specified, null, null); Configuration.MemberVisitor(specifiedMember, specifiedMemberPropertyModel); } @@ -878,7 +869,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi Configuration.MemberVisitor(nullableMember, this); } } - else if ((IsCollection || isArray || (IsList && IsAttribute)) && IsNullable) + else if (isEnumerable && !IsRequired) { var specifiedProperty = new CodeMemberProperty { @@ -915,7 +906,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi typeDeclaration.Members.Add(specifiedProperty); } - if (!IsCollection && isNullableReferenceType && Configuration.EnableNullableReferenceAttributes) + if (IsNullableReferenceType && Configuration.EnableNullableReferenceAttributes) { member.CustomAttributes.Add(new CodeAttributeDeclaration(CodeUtilities.CreateTypeReference(Attributes.AllowNull, Configuration))); member.CustomAttributes.Add(new CodeAttributeDeclaration(CodeUtilities.CreateTypeReference(Attributes.MaybeNull, Configuration))); @@ -925,7 +916,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi member.CustomAttributes.AddRange(attributes); // initialize List<> - if ((IsCollection || isArray || (IsList && IsAttribute)) && Configuration.CollectionSettersMode != CollectionSettersMode.PublicWithoutConstructorInitialization) + if (isEnumerable && Configuration.CollectionSettersMode != CollectionSettersMode.PublicWithoutConstructorInitialization) { var constructor = typeDeclaration.Members.OfType().FirstOrDefault(); @@ -963,7 +954,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi if (isArray) { - var arrayItemProperty = typeClassModel.Properties[0]; + var arrayItemProperty = TypeClassModel.Properties[0]; // HACK: repackage as ArrayItemAttribute foreach (var propertyAttribute in arrayItemProperty.GetAttributes(false, OwningType).ToList()) @@ -1074,7 +1065,7 @@ private IEnumerable GetAttributes(bool isArray, TypeMo } } - if (IsNillable && !(IsCollection && Type is SimpleModel m && m.ValueType.IsValueType) && !(IsNullable && Configuration.DoNotForceIsNullable)) + if (IsNillable && !(IsCollection && Type is SimpleModel m && m.ValueType.IsValueType) && (IsRequired || !Configuration.DoNotForceIsNullable)) args.Add(new("IsNullable", new CodePrimitiveExpression(true))); if (Type is SimpleModel simpleModel && simpleModel.UseDataTypeAttribute) @@ -1357,8 +1348,6 @@ public class GeneratorModel protected const string EqualsMethod = nameof(object.Equals); protected const string HasValue = nameof(Nullable.HasValue); - protected const string GetSet = " { get; set; }"; - protected const string English = "en"; protected const string German = "de";