|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Xml.Schema;
namespace System.Xml.Serialization
{
public class XmlSchemaExporter
{
internal const XmlSchemaForm elementFormDefault = XmlSchemaForm.Qualified;
internal const XmlSchemaForm attributeFormDefault = XmlSchemaForm.Unqualified;
private readonly XmlSchemas _schemas;
private readonly Hashtable _elements = new Hashtable(); // ElementAccessor -> XmlSchemaElement
private readonly Hashtable _attributes = new Hashtable(); // AttributeAccessor -> XmlSchemaElement
private readonly Hashtable _types = new Hashtable(); // StructMapping/EnumMapping -> XmlSchemaComplexType/XmlSchemaSimpleType
private readonly Hashtable _references = new Hashtable(); // TypeMappings to keep track of circular references via anonymous types
private bool _needToExportRoot;
private TypeScope? _scope;
public XmlSchemaExporter(XmlSchemas schemas)
{
_schemas = schemas;
}
public void ExportTypeMapping(XmlTypeMapping xmlTypeMapping)
{
xmlTypeMapping.CheckShallow();
CheckScope(xmlTypeMapping.Scope);
ExportElement(xmlTypeMapping.Accessor);
ExportRootIfNecessary(xmlTypeMapping.Scope!);
}
public XmlQualifiedName? ExportTypeMapping(XmlMembersMapping xmlMembersMapping)
{
xmlMembersMapping.CheckShallow();
CheckScope(xmlMembersMapping.Scope!);
MembersMapping mapping = (MembersMapping)xmlMembersMapping.Accessor.Mapping!;
if (mapping.Members!.Length == 1 && mapping.Members[0].Elements![0].Mapping is SpecialMapping)
{
SpecialMapping special = (SpecialMapping)mapping.Members[0].Elements![0].Mapping!;
XmlSchemaType? type = ExportSpecialMapping(special, xmlMembersMapping.Accessor.Namespace, false, null);
if (type != null && type.Name != null && type.Name.Length > 0)
{
type.Name = xmlMembersMapping.Accessor.Name;
AddSchemaItem(type, xmlMembersMapping.Accessor.Namespace, null);
}
ExportRootIfNecessary(xmlMembersMapping.Scope!);
return (new XmlQualifiedName(xmlMembersMapping.Accessor.Name, xmlMembersMapping.Accessor.Namespace));
}
return null;
}
public void ExportMembersMapping(XmlMembersMapping xmlMembersMapping)
{
ExportMembersMapping(xmlMembersMapping, true);
}
public void ExportMembersMapping(XmlMembersMapping xmlMembersMapping, bool exportEnclosingType)
{
xmlMembersMapping.CheckShallow();
MembersMapping mapping = (MembersMapping)xmlMembersMapping.Accessor.Mapping!;
CheckScope(xmlMembersMapping.Scope);
if (mapping.HasWrapperElement && exportEnclosingType)
{
ExportElement(xmlMembersMapping.Accessor);
}
else
{
foreach (MemberMapping member in mapping.Members!)
{
if (member.Attribute != null)
throw new InvalidOperationException(SR.Format(SR.XmlBareAttributeMember, member.Attribute.Name));
else if (member.Text != null)
throw new InvalidOperationException(SR.Format(SR.XmlBareTextMember, member.Text.Name));
else if (member.Elements == null || member.Elements.Length == 0)
continue;
if (member.TypeDesc!.IsArrayLike && !(member.Elements[0].Mapping is ArrayMapping))
throw new InvalidOperationException(SR.Format(SR.XmlIllegalArrayElement, member.Elements[0].Name));
if (exportEnclosingType)
{
ExportElement(member.Elements[0]);
}
else
{
ExportMapping(member.Elements[0].Mapping!, member.Elements[0].Namespace, member.Elements[0].Any);
}
}
}
ExportRootIfNecessary(xmlMembersMapping.Scope!);
}
private static XmlSchemaType? FindSchemaType(string name, XmlSchemaObjectCollection items)
{
// Have to loop through the items because schema.SchemaTypes has not been populated yet.
foreach (object o in items)
{
XmlSchemaType? type = o as XmlSchemaType;
if (type == null)
continue;
if (type.Name == name)
return type;
}
return null;
}
private static bool IsAnyType(XmlSchemaType schemaType, bool mixed, bool unbounded)
{
XmlSchemaComplexType? complexType = schemaType as XmlSchemaComplexType;
if (complexType != null)
{
if (complexType.IsMixed != mixed)
return false;
if (complexType.Particle is XmlSchemaSequence sequence)
{
if (sequence.Items.Count == 1 && sequence.Items[0] is XmlSchemaAny)
{
XmlSchemaAny any = (XmlSchemaAny)sequence.Items[0];
return (unbounded == any.IsMultipleOccurrence);
}
}
}
return false;
}
public string ExportAnyType(string? ns)
{
string name = "any";
int i = 0;
XmlSchema? schema = _schemas[ns];
if (schema != null)
{
while (true)
{
XmlSchemaType? schemaType = FindSchemaType(name, schema.Items);
if (schemaType == null)
break;
if (IsAnyType(schemaType, true, true))
return name;
i++;
name = string.Create(CultureInfo.InvariantCulture, $"any{i}");
}
}
XmlSchemaComplexType type = new XmlSchemaComplexType();
type.Name = name;
type.IsMixed = true;
XmlSchemaSequence seq = new XmlSchemaSequence();
XmlSchemaAny any = new XmlSchemaAny();
any.MinOccurs = 0;
any.MaxOccurs = decimal.MaxValue;
seq.Items.Add(any);
type.Particle = seq;
AddSchemaItem(type, ns, null);
return name;
}
public string? ExportAnyType(XmlMembersMapping members)
{
if (members.Count == 1 && members[0].Any && members[0].ElementName.Length == 0)
{
XmlMemberMapping member = members[0];
string? ns = member.Namespace;
bool isUnbounded = member.Mapping.TypeDesc!.IsArrayLike;
bool isMixed = isUnbounded && member.Mapping.TypeDesc.ArrayElementTypeDesc != null ? member.Mapping.TypeDesc.ArrayElementTypeDesc.IsMixed : member.Mapping.TypeDesc.IsMixed;
if (isMixed && member.Mapping.TypeDesc.IsMixed)
// special case of the single top-level XmlNode --> map it to node array to match the "mixed" any type for backward compatibility
isUnbounded = true;
// generate type name, make sure that it is backward compatible
string baseName = isMixed ? "any" : isUnbounded ? "anyElements" : "anyElement";
string name = baseName;
int i = 0;
XmlSchema? schema = _schemas[ns];
if (schema != null)
{
while (true)
{
XmlSchemaType? schemaType = FindSchemaType(name, schema.Items);
if (schemaType == null)
break;
if (IsAnyType(schemaType, isMixed, isUnbounded))
return name;
i++;
name = baseName + i.ToString(CultureInfo.InvariantCulture);
}
}
XmlSchemaComplexType type = new XmlSchemaComplexType();
type.Name = name;
type.IsMixed = isMixed;
XmlSchemaSequence seq = new XmlSchemaSequence();
XmlSchemaAny any = new XmlSchemaAny();
any.MinOccurs = 0;
if (isUnbounded)
any.MaxOccurs = decimal.MaxValue;
seq.Items.Add(any);
type.Particle = seq;
AddSchemaItem(type, ns, null);
return name;
}
else
{
return null;
}
}
private void CheckScope(TypeScope? scope)
{
if (_scope == null)
{
_scope = scope;
}
else if (_scope != scope)
{
throw new InvalidOperationException(SR.XmlMappingsScopeMismatch);
}
}
private XmlSchemaElement? ExportElement(ElementAccessor accessor)
{
if (!accessor.Mapping!.IncludeInSchema && !accessor.Mapping.TypeDesc!.IsRoot)
{
return null;
}
if (accessor.Any && accessor.Name.Length == 0)
throw new InvalidOperationException(SR.XmlIllegalWildcard);
XmlSchemaElement? element = (XmlSchemaElement?)_elements[accessor];
if (element != null) return element;
element = new XmlSchemaElement();
element.Name = accessor.Name;
element.IsNillable = accessor.IsNullable;
_elements.Add(accessor, element);
element.Form = accessor.Form;
AddSchemaItem(element, accessor.Namespace, null);
ExportElementMapping(element, accessor.Mapping, accessor.Namespace, accessor.Any);
return element;
}
private void CheckForDuplicateType(TypeMapping mapping, string? newNamespace)
{
if (mapping.IsAnonymousType)
return;
string? newTypeName = mapping.TypeName;
XmlSchema? schema = _schemas[newNamespace];
if (schema != null)
{
foreach (XmlSchemaObject o in schema.Items)
{
XmlSchemaType? type = o as XmlSchemaType;
if (type != null && type.Name == newTypeName)
throw new InvalidOperationException(SR.Format(SR.XmlDuplicateTypeName, newTypeName, newNamespace));
}
}
}
private XmlSchema AddSchema(string? targetNamespace)
{
XmlSchema schema = new XmlSchema();
schema.TargetNamespace = string.IsNullOrEmpty(targetNamespace) ? null : targetNamespace;
#pragma warning disable 429 // Unreachable code: the default values are constant, so will never be Unqualified
schema.ElementFormDefault = elementFormDefault == XmlSchemaForm.Unqualified ? XmlSchemaForm.None : elementFormDefault;
schema.AttributeFormDefault = attributeFormDefault == XmlSchemaForm.Unqualified ? XmlSchemaForm.None : attributeFormDefault;
#pragma warning restore 429
_schemas.Add(schema);
return schema;
}
private void AddSchemaItem(XmlSchemaObject item, string? ns, string? referencingNs)
{
XmlSchema schema = _schemas[ns] ?? AddSchema(ns);
if (item is XmlSchemaElement e)
{
if (e.Form == XmlSchemaForm.Unqualified)
throw new InvalidOperationException(SR.Format(SR.XmlIllegalForm, e.Name));
e.Form = XmlSchemaForm.None;
}
else if (item is XmlSchemaAttribute a)
{
if (a.Form == XmlSchemaForm.Unqualified)
throw new InvalidOperationException(SR.Format(SR.XmlIllegalForm, a.Name));
a.Form = XmlSchemaForm.None;
}
schema.Items.Add(item);
AddSchemaImport(ns, referencingNs);
}
private void AddSchemaImport(string? ns, string? referencingNs)
{
if (referencingNs == null) return;
if (NamespacesEqual(ns, referencingNs)) return;
XmlSchema schema = _schemas[referencingNs] ?? AddSchema(referencingNs);
if (FindImport(schema, ns) == null)
{
XmlSchemaImport import = new XmlSchemaImport();
if (ns != null && ns.Length > 0)
import.Namespace = ns;
schema.Includes.Add(import);
}
}
private static bool NamespacesEqual(string? ns1, string? ns2)
{
if (string.IsNullOrEmpty(ns1))
return string.IsNullOrEmpty(ns2);
else
return ns1 == ns2;
}
private bool SchemaContainsItem(XmlSchemaObject item, string ns)
{
XmlSchema? schema = _schemas[ns];
if (schema != null)
{
return schema.Items.Contains(item);
}
return false;
}
private static XmlSchemaImport? FindImport(XmlSchema schema, string? ns)
{
foreach (object item in schema.Includes)
{
if (item is XmlSchemaImport import)
{
if (NamespacesEqual(import.Namespace, ns))
{
return import;
}
}
}
return null;
}
private void ExportMapping(Mapping mapping, string? ns, bool isAny)
{
if (mapping is ArrayMapping)
ExportArrayMapping((ArrayMapping)mapping, ns, null);
else if (mapping is PrimitiveMapping)
{
ExportPrimitiveMapping((PrimitiveMapping)mapping, ns);
}
else if (mapping is StructMapping)
ExportStructMapping((StructMapping)mapping, ns, null);
else if (mapping is MembersMapping)
ExportMembersMapping((MembersMapping)mapping, ns);
else if (mapping is SpecialMapping)
ExportSpecialMapping((SpecialMapping)mapping, ns, isAny, null);
else if (mapping is NullableMapping)
ExportMapping(((NullableMapping)mapping).BaseMapping!, ns, isAny);
else
throw new ArgumentException(SR.XmlInternalError, nameof(mapping));
}
private void ExportElementMapping(XmlSchemaElement element, Mapping mapping, string? ns, bool isAny)
{
if (mapping is ArrayMapping)
ExportArrayMapping((ArrayMapping)mapping, ns, element);
else if (mapping is PrimitiveMapping pm)
{
if (pm.IsAnonymousType)
{
element.SchemaType = ExportAnonymousPrimitiveMapping(pm);
}
else
{
element.SchemaTypeName = ExportPrimitiveMapping(pm, ns);
}
}
else if (mapping is StructMapping)
{
ExportStructMapping((StructMapping)mapping, ns, element);
}
else if (mapping is MembersMapping)
element.SchemaType = ExportMembersMapping((MembersMapping)mapping, ns);
else if (mapping is SpecialMapping)
ExportSpecialMapping((SpecialMapping)mapping, ns, isAny, element);
else if (mapping is NullableMapping)
{
ExportElementMapping(element, ((NullableMapping)mapping).BaseMapping!, ns, isAny);
}
else
throw new ArgumentException(SR.XmlInternalError, nameof(mapping));
}
private XmlQualifiedName ExportNonXsdPrimitiveMapping(PrimitiveMapping mapping, string? ns)
{
XmlSchemaSimpleType type = (XmlSchemaSimpleType)mapping.TypeDesc!.DataType!;
if (!SchemaContainsItem(type, UrtTypes.Namespace))
{
AddSchemaItem(type, UrtTypes.Namespace, ns);
}
else
{
AddSchemaImport(mapping.Namespace, ns);
}
return new XmlQualifiedName(mapping.TypeDesc.DataType!.Name, UrtTypes.Namespace);
}
private XmlSchemaType? ExportSpecialMapping(SpecialMapping mapping, string? ns, bool isAny, XmlSchemaElement? element)
{
switch (mapping.TypeDesc!.Kind)
{
case TypeKind.Node:
{
XmlSchemaComplexType type = new XmlSchemaComplexType();
type.IsMixed = mapping.TypeDesc.IsMixed;
XmlSchemaSequence seq = new XmlSchemaSequence();
XmlSchemaAny any = new XmlSchemaAny();
if (isAny)
{
type.AnyAttribute = new XmlSchemaAnyAttribute();
type.IsMixed = true;
any.MaxOccurs = decimal.MaxValue;
}
seq.Items.Add(any);
type.Particle = seq;
if (element != null)
element.SchemaType = type;
return type;
}
case TypeKind.Serializable:
{
SerializableMapping serializableMapping = (SerializableMapping)mapping;
if (serializableMapping.IsAny)
{
XmlSchemaComplexType type = new XmlSchemaComplexType();
type.IsMixed = mapping.TypeDesc.IsMixed;
XmlSchemaSequence seq = new XmlSchemaSequence();
XmlSchemaAny any = new XmlSchemaAny();
if (isAny)
{
type.AnyAttribute = new XmlSchemaAnyAttribute();
type.IsMixed = true;
any.MaxOccurs = decimal.MaxValue;
}
if (serializableMapping.NamespaceList.Length > 0)
any.Namespace = serializableMapping.NamespaceList;
any.ProcessContents = XmlSchemaContentProcessing.Lax;
if (serializableMapping.Schemas != null)
{
foreach (XmlSchema schema in serializableMapping.Schemas.Schemas())
{
if (schema.TargetNamespace != XmlSchema.Namespace)
{
_schemas.Add(schema, true);
AddSchemaImport(schema.TargetNamespace, ns);
}
}
}
seq.Items.Add(any);
type.Particle = seq;
if (element != null)
element.SchemaType = type;
return type;
}
else if (serializableMapping.XsiType != null || serializableMapping.XsdType != null)
{
XmlSchemaType? type = serializableMapping.XsdType;
// for performance reasons we need to postpone merging of the serializable schemas
foreach (XmlSchema schema in serializableMapping.Schemas!.Schemas())
{
if (schema.TargetNamespace != XmlSchema.Namespace)
{
_schemas.Add(schema, true);
AddSchemaImport(schema.TargetNamespace, ns);
if (!serializableMapping.XsiType!.IsEmpty && serializableMapping.XsiType.Namespace == schema.TargetNamespace)
type = (XmlSchemaType?)schema.SchemaTypes[serializableMapping.XsiType];
}
}
if (element != null)
{
element.SchemaTypeName = serializableMapping.XsiType!;
if (element.SchemaTypeName.IsEmpty)
element.SchemaType = type;
}
// check for duplicate top-level elements XmlAttributes
serializableMapping.CheckDuplicateElement(element, ns);
return type;
}
else if (serializableMapping.Schema != null)
{
// this is the strongly-typed DataSet
XmlSchemaComplexType type = new XmlSchemaComplexType();
XmlSchemaAny any = new XmlSchemaAny();
XmlSchemaSequence seq = new XmlSchemaSequence();
seq.Items.Add(any);
type.Particle = seq;
string? anyNs = serializableMapping.Schema.TargetNamespace;
any.Namespace = anyNs ?? "";
XmlSchema? existingSchema = _schemas[anyNs];
if (existingSchema == null)
{
_schemas.Add(serializableMapping.Schema);
}
else if (existingSchema != serializableMapping.Schema)
{
throw new InvalidOperationException(SR.Format(SR.XmlDuplicateNamespace, anyNs));
}
if (element != null)
element.SchemaType = type;
// check for duplicate top-level elements XmlAttributes
serializableMapping.CheckDuplicateElement(element, ns);
return type;
}
else
{
// DataSet
XmlSchemaComplexType type = new XmlSchemaComplexType();
XmlSchemaElement schemaElement = new XmlSchemaElement();
schemaElement.RefName = new XmlQualifiedName("schema", XmlSchema.Namespace);
XmlSchemaSequence seq = new XmlSchemaSequence();
seq.Items.Add(schemaElement);
seq.Items.Add(new XmlSchemaAny());
type.Particle = seq;
AddSchemaImport(XmlSchema.Namespace, ns);
if (element != null)
element.SchemaType = type;
return type;
}
}
default:
throw new ArgumentException(SR.XmlInternalError, nameof(mapping));
}
}
private XmlSchemaComplexType ExportMembersMapping(MembersMapping mapping, string? ns)
{
XmlSchemaComplexType type = new XmlSchemaComplexType();
ExportTypeMembers(type, mapping.Members!, mapping.TypeName!, ns, false, false);
if (mapping.XmlnsMember != null)
{
AddXmlnsAnnotation(type, mapping.XmlnsMember.Name);
}
return type;
}
private XmlSchemaSimpleType ExportAnonymousPrimitiveMapping(PrimitiveMapping mapping)
{
if (mapping is EnumMapping)
{
return ExportEnumMapping((EnumMapping)mapping, null);
}
else
{
throw new InvalidOperationException(SR.Format(SR.XmlInternalErrorDetails, $"Unsupported anonymous mapping type: {mapping}"));
}
}
private XmlQualifiedName ExportPrimitiveMapping(PrimitiveMapping mapping, string? ns)
{
XmlQualifiedName qname;
if (mapping is EnumMapping)
{
XmlSchemaType type = ExportEnumMapping((EnumMapping)mapping, ns);
qname = new XmlQualifiedName(type.Name, mapping.Namespace);
}
else
{
if (mapping.TypeDesc!.IsXsdType)
{
qname = new XmlQualifiedName(mapping.TypeDesc.DataType!.Name, XmlSchema.Namespace);
}
else
{
qname = ExportNonXsdPrimitiveMapping(mapping, ns);
}
}
return qname;
}
private void ExportArrayMapping(ArrayMapping mapping, string? ns, XmlSchemaElement? element)
{
// some of the items in the linked list differ only by CLR type. We don't need to
// export different schema types for these. Look further down the list for another
// entry with the same elements. If there is one, it will be exported later so
// just return its name now.
ArrayMapping currentMapping = mapping;
while (currentMapping.Next != null)
{
currentMapping = currentMapping.Next;
}
XmlSchemaComplexType? type = (XmlSchemaComplexType?)_types[currentMapping];
if (type == null)
{
CheckForDuplicateType(currentMapping, currentMapping.Namespace);
type = new XmlSchemaComplexType();
if (!mapping.IsAnonymousType)
{
type.Name = mapping.TypeName;
AddSchemaItem(type, mapping.Namespace, ns);
}
if (!currentMapping.IsAnonymousType)
_types.Add(currentMapping, type);
XmlSchemaSequence seq = new XmlSchemaSequence();
ExportElementAccessors(seq, mapping.Elements!, true, false, mapping.Namespace);
if (seq.Items.Count > 0)
{
#if DEBUG
// we can have only one item for the array mapping
if (seq.Items.Count != 1)
throw new InvalidOperationException(SR.Format(SR.XmlInternalErrorDetails, "Type " + mapping.TypeName + " from namespace '" + ns + "' is an invalid array mapping"));
#endif
if (seq.Items[0] is XmlSchemaChoice)
{
type.Particle = (XmlSchemaChoice)seq.Items[0];
}
else
{
type.Particle = seq;
}
}
}
else
{
AddSchemaImport(mapping.Namespace, ns);
}
if (element != null)
{
if (mapping.IsAnonymousType)
{
element.SchemaType = type;
}
else
{
element.SchemaTypeName = new XmlQualifiedName(type.Name, mapping.Namespace);
}
}
}
private void ExportElementAccessors(XmlSchemaGroupBase group, ElementAccessor[] accessors, bool repeats, bool valueTypeOptional, string? ns)
{
if (accessors.Length == 0) return;
if (accessors.Length == 1)
{
ExportElementAccessor(group, accessors[0], repeats, valueTypeOptional, ns);
}
else
{
XmlSchemaChoice choice = new XmlSchemaChoice();
choice.MaxOccurs = repeats ? decimal.MaxValue : 1;
choice.MinOccurs = repeats ? 0 : 1;
for (int i = 0; i < accessors.Length; i++)
ExportElementAccessor(choice, accessors[i], false, valueTypeOptional, ns);
if (choice.Items.Count > 0) group.Items.Add(choice);
}
}
private void ExportAttributeAccessor(XmlSchemaComplexType type, AttributeAccessor? accessor, bool valueTypeOptional, string? ns)
{
if (accessor == null) return;
XmlSchemaObjectCollection attributes;
if (type.ContentModel != null)
{
if (type.ContentModel.Content is XmlSchemaComplexContentRestriction)
attributes = ((XmlSchemaComplexContentRestriction)type.ContentModel.Content).Attributes;
else if (type.ContentModel.Content is XmlSchemaComplexContentExtension)
attributes = ((XmlSchemaComplexContentExtension)type.ContentModel.Content).Attributes;
else if (type.ContentModel.Content is XmlSchemaSimpleContentExtension)
attributes = ((XmlSchemaSimpleContentExtension)type.ContentModel.Content).Attributes;
else
throw new InvalidOperationException(SR.Format(SR.XmlInvalidContent, type.ContentModel.Content!.GetType().Name));
}
else
{
attributes = type.Attributes;
}
if (accessor.IsSpecialXmlNamespace)
{
// add <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
AddSchemaImport(XmlReservedNs.NsXml, ns);
// generate <xsd:attribute ref="xml:lang" use="optional" />
XmlSchemaAttribute attribute = new XmlSchemaAttribute();
attribute.Use = XmlSchemaUse.Optional;
attribute.RefName = new XmlQualifiedName(accessor.Name, XmlReservedNs.NsXml);
attributes.Add(attribute);
}
else if (accessor.Any)
{
if (type.ContentModel == null)
{
type.AnyAttribute = new XmlSchemaAnyAttribute();
}
else
{
XmlSchemaContent? content = type.ContentModel.Content;
if (content is XmlSchemaComplexContentExtension)
{
XmlSchemaComplexContentExtension extension = (XmlSchemaComplexContentExtension)content;
extension.AnyAttribute = new XmlSchemaAnyAttribute();
}
else if (content is XmlSchemaComplexContentRestriction restriction)
{
restriction.AnyAttribute = new XmlSchemaAnyAttribute();
}
else if (type.ContentModel.Content is XmlSchemaSimpleContentExtension)
{
XmlSchemaSimpleContentExtension extension = (XmlSchemaSimpleContentExtension)content!;
extension.AnyAttribute = new XmlSchemaAnyAttribute();
}
}
}
else
{
XmlSchemaAttribute attribute = new XmlSchemaAttribute();
attribute.Use = XmlSchemaUse.None;
if (!accessor.HasDefault && !valueTypeOptional && accessor.Mapping!.TypeDesc!.IsValueType)
{
attribute.Use = XmlSchemaUse.Required;
}
attribute.Name = accessor.Name;
if (accessor.Namespace == null || accessor.Namespace == ns)
{
// determine the form attribute value
XmlSchema? schema = _schemas[ns];
if (schema == null)
attribute.Form = accessor.Form == attributeFormDefault ? XmlSchemaForm.None : accessor.Form;
else
{
attribute.Form = accessor.Form == schema.AttributeFormDefault ? XmlSchemaForm.None : accessor.Form;
}
attributes.Add(attribute);
}
else
{
// we are going to add the attribute to the top-level items. "use" attribute should not be set
if (_attributes[accessor] == null)
{
attribute.Use = XmlSchemaUse.None;
attribute.Form = accessor.Form;
AddSchemaItem(attribute, accessor.Namespace, ns);
_attributes.Add(accessor, accessor);
}
XmlSchemaAttribute refAttribute = new XmlSchemaAttribute();
refAttribute.Use = XmlSchemaUse.None;
refAttribute.RefName = new XmlQualifiedName(accessor.Name, accessor.Namespace);
attributes.Add(refAttribute);
AddSchemaImport(accessor.Namespace, ns);
}
if (accessor.Mapping is PrimitiveMapping pm)
{
if (pm.IsList)
{
// create local simple type for the list-like attributes
XmlSchemaSimpleType dataType = new XmlSchemaSimpleType();
XmlSchemaSimpleTypeList list = new XmlSchemaSimpleTypeList();
if (pm.IsAnonymousType)
{
list.ItemType = (XmlSchemaSimpleType)ExportAnonymousPrimitiveMapping(pm);
}
else
{
list.ItemTypeName = ExportPrimitiveMapping(pm, accessor.Namespace ?? ns);
}
dataType.Content = list;
attribute.SchemaType = dataType;
}
else
{
if (pm.IsAnonymousType)
{
attribute.SchemaType = (XmlSchemaSimpleType)ExportAnonymousPrimitiveMapping(pm);
}
else
{
attribute.SchemaTypeName = ExportPrimitiveMapping(pm, accessor.Namespace ?? ns);
}
}
}
else if (!(accessor.Mapping is SpecialMapping))
throw new InvalidOperationException(SR.XmlInternalError);
if (accessor.HasDefault)
{
attribute.DefaultValue = ExportDefaultValue(accessor.Mapping, accessor.Default);
}
}
}
private void ExportElementAccessor(XmlSchemaGroupBase group, ElementAccessor accessor, bool repeats, bool valueTypeOptional, string? ns)
{
if (accessor.Any && accessor.Name.Length == 0)
{
XmlSchemaAny any = new XmlSchemaAny();
any.MinOccurs = 0;
any.MaxOccurs = repeats ? decimal.MaxValue : 1;
if (accessor.Namespace != null && accessor.Namespace.Length > 0 && accessor.Namespace != ns)
any.Namespace = accessor.Namespace;
group.Items.Add(any);
}
else
{
XmlSchemaElement? element = (XmlSchemaElement?)_elements[accessor];
int minOccurs = repeats || accessor.HasDefault || (!accessor.IsNullable && !accessor.Mapping!.TypeDesc!.IsValueType) || valueTypeOptional ? 0 : 1;
decimal maxOccurs = repeats || accessor.IsUnbounded ? decimal.MaxValue : 1;
if (element == null)
{
element = new XmlSchemaElement();
element.IsNillable = accessor.IsNullable;
element.Name = accessor.Name;
if (accessor.HasDefault)
element.DefaultValue = ExportDefaultValue(accessor.Mapping!, accessor.Default);
if (accessor.IsTopLevelInSchema)
{
_elements.Add(accessor, element);
element.Form = accessor.Form;
AddSchemaItem(element, accessor.Namespace, ns);
}
else
{
element.MinOccurs = minOccurs;
element.MaxOccurs = maxOccurs;
// determine the form attribute value
XmlSchema? schema = _schemas[ns];
if (schema == null)
element.Form = accessor.Form == elementFormDefault ? XmlSchemaForm.None : accessor.Form;
else
{
element.Form = accessor.Form == schema.ElementFormDefault ? XmlSchemaForm.None : accessor.Form;
}
}
ExportElementMapping(element, (TypeMapping)accessor.Mapping!, accessor.Namespace, accessor.Any);
}
if (accessor.IsTopLevelInSchema)
{
XmlSchemaElement refElement = new XmlSchemaElement();
refElement.RefName = new XmlQualifiedName(accessor.Name, accessor.Namespace);
refElement.MinOccurs = minOccurs;
refElement.MaxOccurs = maxOccurs;
group.Items.Add(refElement);
AddSchemaImport(accessor.Namespace, ns);
}
else
{
group.Items.Add(element);
}
}
}
internal static string? ExportDefaultValue(TypeMapping mapping, object? value)
{
if (!(mapping is PrimitiveMapping))
// should throw, but it will be a breaking change;
return null;
if (value == null || value == DBNull.Value)
return null;
if (mapping is EnumMapping em)
{
#if DEBUG
// use exception in the place of Debug.Assert to avoid throwing asserts from a server process such as aspnet_ewp.exe
if (value.GetType() != typeof(string)) throw new InvalidOperationException(SR.Format(SR.XmlInternalErrorDetails, SR.Format(SR.XmlInvalidDefaultValue, value, value.GetType().FullName)));
#endif
// check the validity of the value
ConstantMapping[] c = em.Constants!;
if (em.IsFlags)
{
string[] names = new string[c.Length];
long[] ids = new long[c.Length];
Hashtable values = new Hashtable();
for (int i = 0; i < c.Length; i++)
{
names[i] = c[i].XmlName;
ids[i] = 1 << i;
values.Add(c[i].Name, ids[i]);
}
long val = XmlCustomFormatter.ToEnum((string)value, values, em.TypeName, false);
return val != 0 ? XmlCustomFormatter.FromEnum(val, names, ids, mapping.TypeDesc!.FullName) : null;
}
else
{
for (int i = 0; i < c.Length; i++)
{
if (c[i].Name == (string)value)
{
return c[i].XmlName;
}
}
return null; // unknown value
}
}
PrimitiveMapping pm = (PrimitiveMapping)mapping;
if (!pm.TypeDesc!.HasCustomFormatter)
{
#if DEBUG
// use exception in the place of Debug.Assert to avoid throwing asserts from a server process such as aspnet_ewp.exe
if (pm.TypeDesc.Type == null)
{
throw new InvalidOperationException(SR.Format(SR.XmlInternalErrorDetails, "Mapping for " + pm.TypeDesc.Name + " missing type property"));
}
#endif
if (pm.TypeDesc.FormatterName == "String")
return (string)value;
Type formatter = typeof(XmlConvert);
System.Reflection.MethodInfo? format = formatter.GetMethod("ToString", new Type[] { pm.TypeDesc.Type! });
if (format != null)
return (string)format.Invoke(formatter, new object[] { value })!;
}
else
{
string defaultValue = XmlCustomFormatter.FromDefaultValue(value, pm.TypeDesc.FormatterName!);
if (defaultValue == null)
throw new InvalidOperationException(SR.Format(SR.XmlInvalidDefaultValue, value, pm.TypeDesc.Name));
return defaultValue;
}
throw new InvalidOperationException(SR.Format(SR.XmlInvalidDefaultValue, value, pm.TypeDesc.Name));
}
private void ExportRootIfNecessary(TypeScope typeScope)
{
if (!_needToExportRoot)
return;
foreach (TypeMapping mapping in typeScope.TypeMappings)
{
if (mapping is StructMapping && mapping.TypeDesc!.IsRoot)
{
ExportDerivedMappings((StructMapping)mapping);
}
else if (mapping is ArrayMapping)
{
ExportArrayMapping((ArrayMapping)mapping, mapping.Namespace, null);
}
else if (mapping is SerializableMapping)
{
ExportSpecialMapping((SerializableMapping)mapping, mapping.Namespace, false, null);
}
}
}
private XmlQualifiedName ExportStructMapping(StructMapping mapping, string? ns, XmlSchemaElement? element)
{
if (mapping.TypeDesc!.IsRoot)
{
_needToExportRoot = true;
return XmlQualifiedName.Empty;
}
if (mapping.IsAnonymousType)
{
if (_references[mapping] != null)
throw new InvalidOperationException(SR.Format(SR.XmlCircularReference2, mapping.TypeDesc.Name, "AnonymousType", "false"));
_references[mapping] = mapping;
}
XmlSchemaComplexType? type = (XmlSchemaComplexType?)_types[mapping];
if (type == null)
{
if (!mapping.IncludeInSchema) throw new InvalidOperationException(SR.Format(SR.XmlCannotIncludeInSchema, mapping.TypeDesc.Name));
CheckForDuplicateType(mapping, mapping.Namespace);
type = new XmlSchemaComplexType();
if (!mapping.IsAnonymousType)
{
type.Name = mapping.TypeName;
AddSchemaItem(type, mapping.Namespace, ns);
_types.Add(mapping, type);
}
type.IsAbstract = mapping.TypeDesc.IsAbstract;
bool openModel = mapping.IsOpenModel;
if (mapping.BaseMapping != null && mapping.BaseMapping.IncludeInSchema)
{
if (mapping.BaseMapping.IsAnonymousType)
{
throw new InvalidOperationException(SR.Format(SR.XmlAnonymousBaseType, mapping.TypeDesc.Name, mapping.BaseMapping.TypeDesc!.Name, "AnonymousType", "false"));
}
if (mapping.HasSimpleContent)
{
XmlSchemaSimpleContent model = new XmlSchemaSimpleContent();
XmlSchemaSimpleContentExtension extension = new XmlSchemaSimpleContentExtension();
extension.BaseTypeName = ExportStructMapping(mapping.BaseMapping, mapping.Namespace, null);
model.Content = extension;
type.ContentModel = model;
}
else
{
XmlSchemaComplexContentExtension extension = new XmlSchemaComplexContentExtension();
extension.BaseTypeName = ExportStructMapping(mapping.BaseMapping, mapping.Namespace, null);
XmlSchemaComplexContent model = new XmlSchemaComplexContent();
model.Content = extension;
model.IsMixed = XmlSchemaImporter.IsMixed((XmlSchemaComplexType)_types[mapping.BaseMapping]!);
type.ContentModel = model;
}
openModel = false;
}
ExportTypeMembers(type, mapping.Members!, mapping.TypeName!, mapping.Namespace, mapping.HasSimpleContent, openModel);
ExportDerivedMappings(mapping);
if (mapping.XmlnsMember != null)
{
AddXmlnsAnnotation(type, mapping.XmlnsMember.Name);
}
}
else
{
AddSchemaImport(mapping.Namespace, ns);
}
if (mapping.IsAnonymousType)
{
_references[mapping] = null;
if (element != null)
element.SchemaType = type;
return XmlQualifiedName.Empty;
}
else
{
XmlQualifiedName qname = new XmlQualifiedName(type.Name, mapping.Namespace);
if (element != null) element.SchemaTypeName = qname;
return qname;
}
}
private void ExportTypeMembers(XmlSchemaComplexType type, MemberMapping[] members, string name, string? ns, bool hasSimpleContent, bool openModel)
{
XmlSchemaGroupBase seq = new XmlSchemaSequence();
TypeMapping? textMapping = null;
for (int i = 0; i < members.Length; i++)
{
MemberMapping member = members[i];
if (member.Ignore)
continue;
if (member.Text != null)
{
if (textMapping != null)
{
throw new InvalidOperationException(SR.Format(SR.XmlIllegalMultipleText, name));
}
textMapping = member.Text.Mapping;
}
if (member.Elements!.Length > 0)
{
bool repeats = member.TypeDesc!.IsArrayLike &&
!(member.Elements.Length == 1 && member.Elements[0].Mapping is ArrayMapping);
bool valueTypeOptional = member.CheckSpecified != SpecifiedAccessor.None || member.CheckShouldPersist;
ExportElementAccessors(seq, member.Elements, repeats, valueTypeOptional, ns);
}
}
if (seq.Items.Count > 0)
{
if (type.ContentModel != null)
{
if (type.ContentModel.Content is XmlSchemaComplexContentRestriction)
((XmlSchemaComplexContentRestriction)type.ContentModel.Content).Particle = seq;
else if (type.ContentModel.Content is XmlSchemaComplexContentExtension)
((XmlSchemaComplexContentExtension)type.ContentModel.Content).Particle = seq;
else
throw new InvalidOperationException(SR.Format(SR.XmlInvalidContent, type.ContentModel.Content!.GetType().Name));
}
else
{
type.Particle = seq;
}
}
if (textMapping != null)
{
if (hasSimpleContent)
{
if (textMapping is PrimitiveMapping && seq.Items.Count == 0)
{
PrimitiveMapping pm = (PrimitiveMapping)textMapping;
if (pm.IsList)
{
type.IsMixed = true;
}
else
{
if (pm.IsAnonymousType)
{
throw new InvalidOperationException(SR.Format(SR.XmlAnonymousBaseType, textMapping.TypeDesc!.Name, pm.TypeDesc!.Name, "AnonymousType", "false"));
}
// Create simpleContent
XmlSchemaSimpleContent model = new XmlSchemaSimpleContent();
XmlSchemaSimpleContentExtension ex = new XmlSchemaSimpleContentExtension();
model.Content = ex;
type.ContentModel = model;
ex.BaseTypeName = ExportPrimitiveMapping(pm, ns);
}
}
}
else
{
type.IsMixed = true;
}
}
bool anyAttribute = false;
for (int i = 0; i < members.Length; i++)
{
AttributeAccessor? accessor = members[i].Attribute;
if (accessor != null)
{
ExportAttributeAccessor(type, members[i].Attribute, members[i].CheckSpecified != SpecifiedAccessor.None || members[i].CheckShouldPersist, ns);
if (members[i].Attribute!.Any)
anyAttribute = true;
}
}
if (openModel && !anyAttribute)
{
AttributeAccessor any = new AttributeAccessor();
any.Any = true;
ExportAttributeAccessor(type, any, false, ns);
}
}
private void ExportDerivedMappings(StructMapping mapping)
{
if (mapping.IsAnonymousType)
return;
for (StructMapping? derived = mapping.DerivedMappings; derived != null; derived = derived.NextDerivedMapping)
{
if (derived.IncludeInSchema) ExportStructMapping(derived, derived.Namespace, null);
}
}
private XmlSchemaSimpleType ExportEnumMapping(EnumMapping mapping, string? ns)
{
if (!mapping.IncludeInSchema) throw new InvalidOperationException(SR.Format(SR.XmlCannotIncludeInSchema, mapping.TypeDesc!.Name));
XmlSchemaSimpleType? dataType = (XmlSchemaSimpleType?)_types[mapping];
if (dataType == null)
{
CheckForDuplicateType(mapping, mapping.Namespace);
dataType = new XmlSchemaSimpleType();
dataType.Name = mapping.TypeName;
if (!mapping.IsAnonymousType)
{
_types.Add(mapping, dataType);
AddSchemaItem(dataType, mapping.Namespace, ns);
}
XmlSchemaSimpleTypeRestriction restriction = new XmlSchemaSimpleTypeRestriction();
restriction.BaseTypeName = new XmlQualifiedName("string", XmlSchema.Namespace);
for (int i = 0; i < mapping.Constants!.Length; i++)
{
ConstantMapping constant = mapping.Constants[i];
XmlSchemaEnumerationFacet enumeration = new XmlSchemaEnumerationFacet();
enumeration.Value = constant.XmlName;
restriction.Facets.Add(enumeration);
}
if (!mapping.IsFlags)
{
dataType.Content = restriction;
}
else
{
XmlSchemaSimpleTypeList list = new XmlSchemaSimpleTypeList();
XmlSchemaSimpleType enumType = new XmlSchemaSimpleType();
enumType.Content = restriction;
list.ItemType = enumType;
dataType.Content = list;
}
}
if (!mapping.IsAnonymousType)
{
AddSchemaImport(mapping.Namespace, ns);
}
return dataType;
}
private static void AddXmlnsAnnotation(XmlSchemaComplexType type, string xmlnsMemberName)
{
XmlSchemaAnnotation annotation = new XmlSchemaAnnotation();
XmlSchemaAppInfo appinfo = new XmlSchemaAppInfo();
XmlDocument d = new XmlDocument();
XmlElement e = d.CreateElement("keepNamespaceDeclarations");
if (xmlnsMemberName != null)
e.InsertBefore(d.CreateTextNode(xmlnsMemberName), null);
appinfo.Markup = new XmlNode[] { e };
annotation.Items.Add(appinfo);
type.Annotation = annotation;
}
}
}
|