File: System\Xml\Serialization\SoapSchemaExporter.cs
Project: ndp\fx\src\Xml\System.Xml.csproj (System.Xml)
//------------------------------------------------------------------------------
// <copyright file="SoapSchemaExporter.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>                                                                
//------------------------------------------------------------------------------
 
namespace System.Xml.Serialization {
    
    using System;
    using System.Collections;
    using System.Xml.Schema;
    using System.Xml;
    using System.ComponentModel;
    using System.Diagnostics;
 
    /// <include file='doc\SoapSchemaExporter.uex' path='docs/doc[@for="SoapSchemaExporter"]/*' />
    /// <internalonly/>
    public class SoapSchemaExporter {
        internal const XmlSchemaForm elementFormDefault = XmlSchemaForm.Qualified;
        XmlSchemas schemas;
        Hashtable types = new Hashtable();      // StructMapping/EnumMapping -> XmlSchemaComplexType/XmlSchemaSimpleType
        bool exportedRoot;
        TypeScope scope;
        XmlDocument document;
 
        static XmlQualifiedName ArrayQName = new XmlQualifiedName(Soap.Array, Soap.Encoding);
        static XmlQualifiedName ArrayTypeQName = new XmlQualifiedName(Soap.ArrayType, Soap.Encoding);
        
        /// <include file='doc\SoapSchemaExporter.uex' path='docs/doc[@for="SoapSchemaExporter.SoapSchemaExporter"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public SoapSchemaExporter(XmlSchemas schemas) {
            this.schemas = schemas;
        }
 
        /// <include file='doc\SoapSchemaExporter.uex' path='docs/doc[@for="SoapSchemaExporter.ExportTypeMapping"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public void ExportTypeMapping(XmlTypeMapping xmlTypeMapping) {
            CheckScope(xmlTypeMapping.Scope);
            ExportTypeMapping(xmlTypeMapping.Mapping, null);
        }
 
        /// <include file='doc\SoapSchemaExporter.uex' path='docs/doc[@for="SoapSchemaExporter.ExportMembersMapping"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public void ExportMembersMapping(XmlMembersMapping xmlMembersMapping) {
            ExportMembersMapping(xmlMembersMapping, false);
        }
 
        /// <include file='doc\SoapSchemaExporter.uex' path='docs/doc[@for="SoapSchemaExporter.ExportMembersMapping1"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public void ExportMembersMapping(XmlMembersMapping xmlMembersMapping, bool exportEnclosingType) {
            CheckScope(xmlMembersMapping.Scope);
            MembersMapping membersMapping = (MembersMapping)xmlMembersMapping.Accessor.Mapping;
            if (exportEnclosingType) {
                ExportTypeMapping(membersMapping, null);
            }
            else {
                foreach (MemberMapping memberMapping in membersMapping.Members) {
                    if (memberMapping.Elements.Length > 0) 
                        ExportTypeMapping(memberMapping.Elements[0].Mapping, null);
                }
            }
        }
 
        void CheckScope(TypeScope scope) {
            if (this.scope == null) {
                this.scope = scope;
            }
            else if (this.scope != scope) {
                throw new InvalidOperationException(Res.GetString(Res.XmlMappingsScopeMismatch));
            }
        }
 
        internal XmlDocument Document {
            get { 
                if (document == null) document = new XmlDocument();
                return document; 
            }
        }
 
        void CheckForDuplicateType(string newTypeName, string newNamespace){
            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(Res.GetString(Res.XmlDuplicateTypeName, newTypeName, newNamespace));
 
                }
            }
        }
 
        void AddSchemaItem(XmlSchemaObject item, string ns, string referencingNs) {
            if (!SchemaContainsItem(item, ns)) {
                XmlSchema schema = schemas[ns];
                if (schema == null) {
                    schema = new XmlSchema();
                    schema.TargetNamespace = ns == null || ns.Length == 0 ? null : ns;
 
#pragma warning disable 429   // unreachable code detected:  elementFormDefault is const so it will never be Unqualified
                    schema.ElementFormDefault = elementFormDefault == XmlSchemaForm.Unqualified ? XmlSchemaForm.None : elementFormDefault;
#pragma warning restore 429
                    schemas.Add(schema);
                }
                schema.Items.Add(item);
            }
            if (referencingNs != null)
                AddSchemaImport(ns, referencingNs);
        }
 
        void AddSchemaImport(string ns, string referencingNs) {
            if (referencingNs == null || ns == null) return;
            if (ns == referencingNs) return;
            XmlSchema schema = schemas[referencingNs];
            if (schema == null) throw new InvalidOperationException(Res.GetString(Res.XmlMissingSchema, referencingNs));
            if (ns != null && ns.Length > 0 && FindImport(schema, ns) == null) {
                XmlSchemaImport import = new XmlSchemaImport();
                import.Namespace = ns;
                schema.Includes.Add(import);
            }
        }
 
        bool SchemaContainsItem(XmlSchemaObject item, string ns) {
            XmlSchema schema = schemas[ns];
            if (schema != null) {
                return schema.Items.Contains(item);
            }
            return false;
        }
 
        XmlSchemaImport FindImport(XmlSchema schema, string ns) {
            foreach (object item in schema.Includes) {
                if (item is XmlSchemaImport) {
                    XmlSchemaImport import = (XmlSchemaImport)item;
                    if (import.Namespace == ns) {
                        return import;
                    }
                }
            }
            return null;
        }
 
        XmlQualifiedName ExportTypeMapping(TypeMapping mapping, string ns) {
            if (mapping is ArrayMapping)
                 return ExportArrayMapping((ArrayMapping)mapping, ns);
            else if (mapping is EnumMapping)
                return ExportEnumMapping((EnumMapping)mapping, ns);
            else if (mapping is PrimitiveMapping) {
                PrimitiveMapping pm = (PrimitiveMapping)mapping;
                if (pm.TypeDesc.IsXsdType) {
                    return ExportPrimitiveMapping(pm);
                }
                else {
                    return ExportNonXsdPrimitiveMapping(pm, ns);
                }
            }
            else if (mapping is StructMapping)
                return ExportStructMapping((StructMapping)mapping, ns);
            else if (mapping is NullableMapping)
                return ExportTypeMapping(((NullableMapping)mapping).BaseMapping, ns);
            else if (mapping is MembersMapping)
                return ExportMembersMapping((MembersMapping)mapping, ns);
            else
                throw new ArgumentException(Res.GetString(Res.XmlInternalError), "mapping");
        }
 
        XmlQualifiedName ExportNonXsdPrimitiveMapping(PrimitiveMapping mapping, string ns) {
            XmlSchemaType type = mapping.TypeDesc.DataType;
            if (!SchemaContainsItem(type, UrtTypes.Namespace)) {
                AddSchemaItem(type, UrtTypes.Namespace, ns);
            }
            else {
                AddSchemaImport(UrtTypes.Namespace, ns);
            }
            return new XmlQualifiedName(mapping.TypeDesc.DataType.Name, UrtTypes.Namespace);
        }
 
        XmlQualifiedName ExportPrimitiveMapping(PrimitiveMapping mapping) {
            return new XmlQualifiedName(mapping.TypeDesc.DataType.Name, XmlSchema.Namespace);
        }
 
        XmlQualifiedName ExportArrayMapping(ArrayMapping mapping, string ns) {
            // for the Rpc ArrayMapping  different mappings could have the same schema type
            // we link all mappings corresponding to the same type together
            // loop through all mapping that will map to the same complexType, and export only one, 
            // the obvious choice is the last one.
            while (mapping.Next != null) {
                mapping = mapping.Next;
            }
 
            XmlSchemaComplexType type = (XmlSchemaComplexType)types[mapping];
            if (type == null) {
                CheckForDuplicateType(mapping.TypeName, mapping.Namespace);
                type = new XmlSchemaComplexType();
                type.Name = mapping.TypeName;
                types.Add(mapping, type);
 
                // we need to add the type first, to make sure that the schema get created
                AddSchemaItem(type, mapping.Namespace, ns);
                AddSchemaImport(Soap.Encoding, mapping.Namespace);
                AddSchemaImport(Wsdl.Namespace, mapping.Namespace);
 
                XmlSchemaComplexContentRestriction restriction = new XmlSchemaComplexContentRestriction();
                XmlQualifiedName qname = ExportTypeMapping(mapping.Elements[0].Mapping, mapping.Namespace);
 
                if (qname.IsEmpty) {
                    // this is a root mapping
                    qname = new XmlQualifiedName(Soap.UrType, XmlSchema.Namespace);
                }
                //<attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:float[]"/> 
                XmlSchemaAttribute attr = new XmlSchemaAttribute();
                attr.RefName = ArrayTypeQName;
                XmlAttribute attribute = new XmlAttribute("wsdl", Wsdl.ArrayType, Wsdl.Namespace, Document);
                attribute.Value = qname.Namespace + ":" + qname.Name + "[]";
 
                attr.UnhandledAttributes = new XmlAttribute[] {attribute};
                restriction.Attributes.Add(attr);
                restriction.BaseTypeName = ArrayQName;
                XmlSchemaComplexContent model = new XmlSchemaComplexContent();
                model.Content = restriction;
                type.ContentModel = model;
                if (qname.Namespace != XmlSchema.Namespace)
                    AddSchemaImport(qname.Namespace, mapping.Namespace);
            }
            else {
                AddSchemaImport(mapping.Namespace, ns);
            }
            return new XmlQualifiedName(mapping.TypeName, mapping.Namespace);
        }
 
        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);
            }
        }
 
 
        void ExportElementAccessor(XmlSchemaGroupBase group, ElementAccessor accessor, bool repeats, bool valueTypeOptional, string ns) {
            XmlSchemaElement element = new XmlSchemaElement();
            element.MinOccurs = repeats || valueTypeOptional ? 0 : 1;
            element.MaxOccurs = repeats ? decimal.MaxValue : 1;
            element.Name = accessor.Name;
            element.IsNillable = accessor.IsNullable || accessor.Mapping is NullableMapping;
            element.Form = XmlSchemaForm.Unqualified;
            element.SchemaTypeName = ExportTypeMapping(accessor.Mapping, accessor.Namespace);
 
            group.Items.Add(element);
        }
 
        XmlQualifiedName ExportRootMapping(StructMapping mapping) {
            if (!exportedRoot) {
                exportedRoot = true;
                ExportDerivedMappings(mapping);
            }
            return new XmlQualifiedName(Soap.UrType, XmlSchema.Namespace);
        }
 
        XmlQualifiedName ExportStructMapping(StructMapping mapping, string ns) {
            if (mapping.TypeDesc.IsRoot) return ExportRootMapping(mapping);
            XmlSchemaComplexType type = (XmlSchemaComplexType)types[mapping];
            if (type == null) {
                if (!mapping.IncludeInSchema) throw new InvalidOperationException(Res.GetString(Res.XmlSoapCannotIncludeInSchema, mapping.TypeDesc.Name));
                CheckForDuplicateType(mapping.TypeName, mapping.Namespace);
                type = new XmlSchemaComplexType();
                type.Name = mapping.TypeName;
                types.Add(mapping, type);
                AddSchemaItem(type, mapping.Namespace, ns);
                type.IsAbstract = mapping.TypeDesc.IsAbstract;
 
                if (mapping.BaseMapping != null && mapping.BaseMapping.IncludeInSchema) {
                    XmlSchemaComplexContentExtension extension = new XmlSchemaComplexContentExtension();
                    extension.BaseTypeName = ExportStructMapping(mapping.BaseMapping, mapping.Namespace);
                    XmlSchemaComplexContent model = new XmlSchemaComplexContent();
                    model.Content = extension;
                    type.ContentModel = model;
                }
                ExportTypeMembers(type, mapping.Members, mapping.Namespace);
                ExportDerivedMappings(mapping);
            }
            else {
                AddSchemaImport(mapping.Namespace, ns);
            }
            return new XmlQualifiedName(type.Name, mapping.Namespace);
        }
 
        XmlQualifiedName ExportMembersMapping(MembersMapping mapping, string ns) {
            XmlSchemaComplexType type = (XmlSchemaComplexType)types[mapping];
            if(type == null) {
                CheckForDuplicateType(mapping.TypeName, mapping.Namespace);
                type = new XmlSchemaComplexType();
                type.Name = mapping.TypeName;
                types.Add(mapping, type);
                AddSchemaItem(type, mapping.Namespace, ns);
                ExportTypeMembers(type, mapping.Members, mapping.Namespace);
            }
            else {
                AddSchemaImport(mapping.Namespace, ns);
            }
            return new XmlQualifiedName(type.Name, mapping.Namespace);
        }
 
        void ExportTypeMembers(XmlSchemaComplexType type, MemberMapping[] members, string ns) {
            XmlSchemaGroupBase seq = new XmlSchemaSequence();
            for (int i = 0; i < members.Length; i++) {
                MemberMapping member = members[i];
                if (member.Elements.Length > 0) {
                    bool valueTypeOptional = member.CheckSpecified != SpecifiedAccessor.None || member.CheckShouldPersist || !member.TypeDesc.IsValueType; 
                    ExportElementAccessors(seq, member.Elements, false, valueTypeOptional, ns);
                }
            }
            if (seq.Items.Count > 0) {
                if (type.ContentModel != null) {
                    if (type.ContentModel.Content is XmlSchemaComplexContentExtension)
                        ((XmlSchemaComplexContentExtension)type.ContentModel.Content).Particle = seq;
                    else if (type.ContentModel.Content is XmlSchemaComplexContentRestriction)
                        ((XmlSchemaComplexContentRestriction)type.ContentModel.Content).Particle = seq;
                    else
                        throw new InvalidOperationException(Res.GetString(Res.XmlInvalidContent, type.ContentModel.Content.GetType().Name));
                }
                else {
                    type.Particle = seq;
                }
            }
        }
 
        void ExportDerivedMappings(StructMapping mapping) {
            for (StructMapping derived = mapping.DerivedMappings; derived != null; derived = derived.NextDerivedMapping) {
                if (derived.IncludeInSchema) ExportStructMapping(derived, mapping.TypeDesc.IsRoot ? null : mapping.Namespace);
            }
        }
 
        XmlQualifiedName ExportEnumMapping(EnumMapping mapping, string ns) {
            XmlSchemaSimpleType dataType = (XmlSchemaSimpleType)types[mapping];
            if (dataType == null) {
                CheckForDuplicateType(mapping.TypeName, mapping.Namespace);
                dataType = new XmlSchemaSimpleType();
                dataType.Name = mapping.TypeName;
                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;
                }
            }
            else {
                AddSchemaImport(mapping.Namespace, ns);
            }
            return new XmlQualifiedName(mapping.TypeName, mapping.Namespace);
        }
    }
}