File: System\Xml\Schema\XmlSchema.cs
Web Access
Project: src\src\libraries\System.Private.Xml\src\System.Private.Xml.csproj (System.Private.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Xml.Serialization;
 
namespace System.Xml.Schema
{
    [XmlRoot("schema", Namespace = XmlSchema.Namespace)]
    public class XmlSchema : XmlSchemaObject
    {
        public const string Namespace = XmlReservedNs.NsXs;
        public const string InstanceNamespace = XmlReservedNs.NsXsi;
 
        private XmlSchemaForm _attributeFormDefault = XmlSchemaForm.None;
        private XmlSchemaForm _elementFormDefault = XmlSchemaForm.None;
        private XmlSchemaDerivationMethod _blockDefault = XmlSchemaDerivationMethod.None;
        private XmlSchemaDerivationMethod _finalDefault = XmlSchemaDerivationMethod.None;
        private string? _targetNs;
        private string? _version;
        private XmlSchemaObjectCollection _includes = new XmlSchemaObjectCollection();
        private XmlSchemaObjectCollection _items = new XmlSchemaObjectCollection();
        private string? _id;
        private XmlAttribute[]? _moreAttributes;
 
        // compiled info
        private bool _isCompiled;
        private bool _isCompiledBySet;
        private bool _isPreprocessed;
        private bool _isRedefined;
        private int _errorCount;
        private XmlSchemaObjectTable? _attributes;
        private XmlSchemaObjectTable _attributeGroups = new XmlSchemaObjectTable();
        private XmlSchemaObjectTable _elements = new XmlSchemaObjectTable();
        private XmlSchemaObjectTable _types = new XmlSchemaObjectTable();
        private readonly XmlSchemaObjectTable _groups = new XmlSchemaObjectTable();
        private readonly XmlSchemaObjectTable _notations = new XmlSchemaObjectTable();
        private readonly XmlSchemaObjectTable _identityConstraints = new XmlSchemaObjectTable();
 
        private static int s_globalIdCounter = -1;
        private ArrayList? _importedSchemas;
        private ArrayList? _importedNamespaces;
 
        private int _schemaId = -1; //Not added to a set
        private Uri? _baseUri;
        private bool _isChameleon;
        private readonly Hashtable _ids = new Hashtable();
        private XmlDocument? _document;
        private XmlNameTable? _nameTable;
 
        public XmlSchema() { }
 
        public static XmlSchema? Read(TextReader reader, ValidationEventHandler? validationEventHandler)
        {
            ArgumentNullException.ThrowIfNull(reader);
            return Read(new XmlTextReader(reader), validationEventHandler);
        }
 
        public static XmlSchema? Read(Stream stream, ValidationEventHandler? validationEventHandler)
        {
            ArgumentNullException.ThrowIfNull(stream);
            return Read(new XmlTextReader(stream), validationEventHandler);
        }
 
        public static XmlSchema? Read(XmlReader reader, ValidationEventHandler? validationEventHandler)
        {
            ArgumentNullException.ThrowIfNull(reader);
            XmlNameTable nameTable = reader.NameTable;
            Parser parser = new Parser(SchemaType.XSD, nameTable, new SchemaNames(nameTable), validationEventHandler);
            try
            {
                parser.Parse(reader, null);
            }
            catch (XmlSchemaException e)
            {
                if (validationEventHandler != null)
                {
                    validationEventHandler(null, new ValidationEventArgs(e));
                }
                else
                {
                    throw;
                }
                return null;
            }
            return parser.XmlSchema;
        }
 
        [RequiresUnreferencedCode(XmlSerializer.TrimSerializationWarning)]
        [RequiresDynamicCode(XmlSerializer.AotSerializationWarning)]
        public void Write(Stream stream)
        {
            Write(stream, null);
        }
 
        [RequiresUnreferencedCode(XmlSerializer.TrimSerializationWarning)]
        [RequiresDynamicCode(XmlSerializer.AotSerializationWarning)]
        public void Write(Stream stream, XmlNamespaceManager? namespaceManager)
        {
            ArgumentNullException.ThrowIfNull(stream);
            XmlTextWriter xmlWriter = new XmlTextWriter(stream, null);
            xmlWriter.Formatting = Formatting.Indented;
            Write(xmlWriter, namespaceManager);
        }
 
        [RequiresUnreferencedCode(XmlSerializer.TrimSerializationWarning)]
        [RequiresDynamicCode(XmlSerializer.AotSerializationWarning)]
        public void Write(TextWriter writer)
        {
            ArgumentNullException.ThrowIfNull(writer);
            Write(writer, null);
        }
 
        [RequiresUnreferencedCode(XmlSerializer.TrimSerializationWarning)]
        [RequiresDynamicCode(XmlSerializer.AotSerializationWarning)]
        public void Write(TextWriter writer, XmlNamespaceManager? namespaceManager)
        {
            ArgumentNullException.ThrowIfNull(writer);
            XmlTextWriter xmlWriter = new XmlTextWriter(writer);
            xmlWriter.Formatting = Formatting.Indented;
            Write(xmlWriter, namespaceManager);
        }
 
        [RequiresUnreferencedCode(XmlSerializer.TrimSerializationWarning)]
        [RequiresDynamicCode(XmlSerializer.AotSerializationWarning)]
        public void Write(XmlWriter writer)
        {
            ArgumentNullException.ThrowIfNull(writer);
            Write(writer, null);
        }
 
        [DynamicDependency(TrimmerConstants.PublicMembers, typeof(XmlSchema))]
        // This method may be safe given the above Dynamic Dependency but it is not yet fully understood if just preserving
        // all of XmlSchema public members is enough in order to be safe in all cases, so we have opted to keep the RequiresUnreferencedCode
        // attribute for now. This can be removed in the future if it is determined that the above is enough for all scenarios to be trim-safe.
        [RequiresUnreferencedCode(XmlSerializer.TrimSerializationWarning)]
        [RequiresDynamicCode(XmlSerializer.AotSerializationWarning)]
        public void Write(XmlWriter writer, XmlNamespaceManager? namespaceManager)
        {
            ArgumentNullException.ThrowIfNull(writer);
            XmlSerializer serializer = new XmlSerializer(typeof(XmlSchema));
            XmlSerializerNamespaces ns;
 
            if (namespaceManager != null)
            {
                ns = new XmlSerializerNamespaces();
                bool ignoreXS = false;
                if (this.Namespaces != null)
                { //User may have set both nsManager and Namespaces property on the XmlSchema object
                    ignoreXS = this.Namespaces.TryLookupPrefix("xs", out _) || this.Namespaces.TryLookupNamespace(XmlReservedNs.NsXs, out _);
                }
                if (!ignoreXS && namespaceManager.LookupPrefix(XmlReservedNs.NsXs) == null &&
                    namespaceManager.LookupNamespace("xs") == null)
                {
                    ns.Add("xs", XmlReservedNs.NsXs);
                }
                foreach (string prefix in namespaceManager)
                {
                    if (prefix != "xml" && prefix != "xmlns")
                    {
                        ns.Add(prefix, namespaceManager.LookupNamespace(prefix!));
                    }
                }
            }
            else if (this.Namespaces != null && this.Namespaces.Count > 0)
            {
                if (!this.Namespaces.TryLookupPrefix("xs", out _) && !this.Namespaces.TryLookupNamespace(XmlReservedNs.NsXs, out _))
                { //Prefix xs not defined AND schema namespace not already mapped to a prefix
                    this.Namespaces.Add("xs", XmlReservedNs.NsXs);
                }
                ns = this.Namespaces;
            }
            else
            {
                ns = new XmlSerializerNamespaces();
                ns.Add("xs", XmlSchema.Namespace);
                if (!string.IsNullOrEmpty(_targetNs))
                {
                    ns.Add("tns", _targetNs);
                }
            }
            serializer.Serialize(writer, this, ns);
        }
 
        [Obsolete("XmlSchema.Compile has been deprecated. Use System.Xml.Schema.XmlSchemaSet for schema compilation and validation.")]
        public void Compile(ValidationEventHandler? validationEventHandler)
        {
            SchemaInfo sInfo = new SchemaInfo();
            sInfo.SchemaType = SchemaType.XSD;
            CompileSchema(null, null, sInfo, null, validationEventHandler, NameTable, false);
        }
 
        [Obsolete("XmlSchema.Compile has been deprecated. Use System.Xml.Schema.XmlSchemaSet for schema compilation and validation.")]
        public void Compile(ValidationEventHandler? validationEventHandler, XmlResolver? resolver)
        {
            SchemaInfo sInfo = new SchemaInfo();
            sInfo.SchemaType = SchemaType.XSD;
            CompileSchema(null, resolver, sInfo, null, validationEventHandler, NameTable, false);
        }
 
#pragma warning disable 618
        internal bool CompileSchema(XmlSchemaCollection? xsc, XmlResolver? resolver, SchemaInfo schemaInfo, string? ns, ValidationEventHandler? validationEventHandler, XmlNameTable nameTable, bool CompileContentModel)
        {
            //Need to lock here to prevent multi-threading problems when same schema is added to set and compiled
            lock (this)
            {
                //Preprocessing
                SchemaCollectionPreprocessor prep = new SchemaCollectionPreprocessor(nameTable, null, validationEventHandler);
                prep.XmlResolver = resolver;
                if (!prep.Execute(this, ns, true, xsc))
                {
                    return false;
                }
 
                //Compilation
                SchemaCollectionCompiler compiler = new SchemaCollectionCompiler(nameTable, validationEventHandler);
                _isCompiled = compiler.Execute(this, schemaInfo, CompileContentModel);
                this.SetIsCompiled(_isCompiled);
                return _isCompiled;
            }
        }
#pragma warning restore 618
 
        internal void CompileSchemaInSet(XmlNameTable nameTable, ValidationEventHandler? eventHandler, XmlSchemaCompilationSettings? compilationSettings)
        {
            Debug.Assert(_isPreprocessed);
            Compiler setCompiler = new Compiler(nameTable, eventHandler, null, compilationSettings);
            setCompiler.Prepare(this, true);
            _isCompiledBySet = setCompiler.Compile();
        }
 
        [XmlAttribute("attributeFormDefault"), DefaultValue(XmlSchemaForm.None)]
        public XmlSchemaForm AttributeFormDefault
        {
            get { return _attributeFormDefault; }
            set { _attributeFormDefault = value; }
        }
 
        [XmlAttribute("blockDefault"), DefaultValue(XmlSchemaDerivationMethod.None)]
        public XmlSchemaDerivationMethod BlockDefault
        {
            get { return _blockDefault; }
            set { _blockDefault = value; }
        }
 
        [XmlAttribute("finalDefault"), DefaultValue(XmlSchemaDerivationMethod.None)]
        public XmlSchemaDerivationMethod FinalDefault
        {
            get { return _finalDefault; }
            set { _finalDefault = value; }
        }
 
        [XmlAttribute("elementFormDefault"), DefaultValue(XmlSchemaForm.None)]
        public XmlSchemaForm ElementFormDefault
        {
            get { return _elementFormDefault; }
            set { _elementFormDefault = value; }
        }
 
        [XmlAttribute("targetNamespace", DataType = "anyURI")]
        public string? TargetNamespace
        {
            get { return _targetNs; }
            set { _targetNs = value; }
        }
 
        [XmlAttribute("version", DataType = "token")]
        public string? Version
        {
            get { return _version; }
            set { _version = value; }
        }
 
        [XmlElement("include", typeof(XmlSchemaInclude)),
         XmlElement("import", typeof(XmlSchemaImport)),
         XmlElement("redefine", typeof(XmlSchemaRedefine))]
        public XmlSchemaObjectCollection Includes
        {
            get { return _includes; }
        }
 
        [XmlElement("annotation", typeof(XmlSchemaAnnotation)),
         XmlElement("attribute", typeof(XmlSchemaAttribute)),
         XmlElement("attributeGroup", typeof(XmlSchemaAttributeGroup)),
         XmlElement("complexType", typeof(XmlSchemaComplexType)),
         XmlElement("simpleType", typeof(XmlSchemaSimpleType)),
         XmlElement("element", typeof(XmlSchemaElement)),
         XmlElement("group", typeof(XmlSchemaGroup)),
         XmlElement("notation", typeof(XmlSchemaNotation))]
        public XmlSchemaObjectCollection Items
        {
            get { return _items; }
        }
 
        // Compiled info
        [XmlIgnore]
        public bool IsCompiled
        {
            get
            {
                return _isCompiled || _isCompiledBySet;
            }
        }
 
        [XmlIgnore]
        internal bool IsCompiledBySet
        {
            get { return _isCompiledBySet; }
            set { _isCompiledBySet = value; }
        }
 
        [XmlIgnore]
        internal bool IsPreprocessed
        {
            get { return _isPreprocessed; }
            set { _isPreprocessed = value; }
        }
 
        [XmlIgnore]
        internal bool IsRedefined
        {
            get { return _isRedefined; }
            set { _isRedefined = value; }
        }
 
        [XmlIgnore]
        public XmlSchemaObjectTable Attributes => _attributes ??= new XmlSchemaObjectTable();
 
        [XmlIgnore]
        public XmlSchemaObjectTable AttributeGroups => _attributeGroups ??= new XmlSchemaObjectTable();
 
        [XmlIgnore]
        public XmlSchemaObjectTable SchemaTypes => _types ??= new XmlSchemaObjectTable();
 
        [XmlIgnore]
        public XmlSchemaObjectTable Elements => _elements ??= new XmlSchemaObjectTable();
 
        [XmlAttribute("id", DataType = "ID")]
        public string? Id
        {
            get { return _id; }
            set { _id = value; }
        }
 
        [XmlAnyAttribute]
        public XmlAttribute[]? UnhandledAttributes
        {
            get { return _moreAttributes; }
            set { _moreAttributes = value; }
        }
 
        [XmlIgnore]
        public XmlSchemaObjectTable Groups
        {
            get { return _groups; }
        }
 
        [XmlIgnore]
        public XmlSchemaObjectTable Notations
        {
            get { return _notations; }
        }
 
        [XmlIgnore]
        internal XmlSchemaObjectTable IdentityConstraints
        {
            get { return _identityConstraints; }
        }
 
        [XmlIgnore]
        internal Uri? BaseUri
        {
            get { return _baseUri; }
            set
            {
                _baseUri = value;
            }
        }
 
        [XmlIgnore]
        // Please be careful with this property. Since it lazy initialized and its value depends on a global state
        //   if it gets called on multiple schemas in a different order the schemas will end up with different IDs
        //   Unfortunately the IDs are used to sort the schemas in the schema set and thus changing the IDs might change
        //   the order which would be a breaking change!!
        // Simply put if you are planning to add or remove a call to this getter you need to be extra carefull
        //   or better don't do it at all.
        internal int SchemaId
        {
            get
            {
                if (_schemaId == -1)
                {
                    _schemaId = Interlocked.Increment(ref s_globalIdCounter);
                }
                return _schemaId;
            }
        }
 
        [XmlIgnore]
        internal bool IsChameleon
        {
            get { return _isChameleon; }
            set { _isChameleon = value; }
        }
 
        [XmlIgnore]
        internal Hashtable Ids
        {
            get { return _ids; }
        }
 
        [XmlIgnore]
        internal XmlDocument Document => _document ??= new XmlDocument();
 
        [XmlIgnore]
        internal int ErrorCount
        {
            get { return _errorCount; }
            set { _errorCount = value; }
        }
 
        internal new XmlSchema Clone()
        {
            XmlSchema that = new XmlSchema();
            that._attributeFormDefault = _attributeFormDefault;
            that._elementFormDefault = _elementFormDefault;
            that._blockDefault = _blockDefault;
            that._finalDefault = _finalDefault;
            that._targetNs = _targetNs;
            that._version = _version;
            that._includes = _includes;
 
            that.Namespaces = this.Namespaces;
            that._items = _items;
            that.BaseUri = this.BaseUri;
 
            SchemaCollectionCompiler.Cleanup(that);
            return that;
        }
 
        internal XmlSchema DeepClone()
        {
            XmlSchema that = new XmlSchema();
            that._attributeFormDefault = _attributeFormDefault;
            that._elementFormDefault = _elementFormDefault;
            that._blockDefault = _blockDefault;
            that._finalDefault = _finalDefault;
            that._targetNs = _targetNs;
            that._version = _version;
            that._isPreprocessed = _isPreprocessed;
            //that.IsProcessing           = this.IsProcessing; //Not sure if this is needed
 
            //Clone its Items
            for (int i = 0; i < _items.Count; ++i)
            {
                XmlSchemaObject newItem;
 
                XmlSchemaComplexType? complexType;
                XmlSchemaElement? element;
                XmlSchemaGroup? group;
 
                if ((complexType = _items[i] as XmlSchemaComplexType) != null)
                {
                    newItem = complexType.Clone(this);
                }
                else if ((element = _items[i] as XmlSchemaElement) != null)
                {
                    newItem = element.Clone(this);
                }
                else if ((group = _items[i] as XmlSchemaGroup) != null)
                {
                    newItem = group.Clone(this);
                }
                else
                {
                    newItem = _items[i].Clone();
                }
                that.Items.Add(newItem);
            }
 
            //Clone Includes
            for (int i = 0; i < _includes.Count; ++i)
            {
                XmlSchemaExternal newInclude = (XmlSchemaExternal)_includes[i].Clone();
                that.Includes.Add(newInclude);
            }
            that.Namespaces = this.Namespaces;
            //that.includes               = this.includes; //Need to verify this is OK for redefines
            that.BaseUri = this.BaseUri;
            return that;
        }
 
        [XmlIgnore]
        internal override string? IdAttribute
        {
            get { return Id; }
            set { Id = value; }
        }
 
        internal void SetIsCompiled(bool isCompiled)
        {
            _isCompiled = isCompiled;
        }
 
        internal override void SetUnhandledAttributes(XmlAttribute[] moreAttributes)
        {
            _moreAttributes = moreAttributes;
        }
 
        internal override void AddAnnotation(XmlSchemaAnnotation annotation)
        {
            _items.Add(annotation);
        }
 
        internal XmlNameTable NameTable => _nameTable ??= new System.Xml.NameTable();
 
        internal ArrayList ImportedSchemas => _importedSchemas ??= new ArrayList();
 
        internal ArrayList ImportedNamespaces => _importedNamespaces ??= new ArrayList();
 
        internal static void GetExternalSchemasList(IList extList, XmlSchema schema)
        {
            Debug.Assert(extList != null && schema != null);
            if (extList.Contains(schema))
            {
                return;
            }
            extList.Add(schema);
            for (int i = 0; i < schema.Includes.Count; ++i)
            {
                XmlSchemaExternal ext = (XmlSchemaExternal)schema.Includes[i];
                if (ext.Schema != null)
                {
                    GetExternalSchemasList(extList, ext.Schema);
                }
            }
        }
 
#if TRUST_COMPILE_STATE
        internal void AddCompiledInfo(SchemaInfo schemaInfo) {
            XmlQualifiedName itemName;
            foreach (XmlSchemaElement element in elements.Values) {
                itemName = element.QualifiedName;
                schemaInfo.TargetNamespaces[itemName.Namespace] = true;
                if (schemaInfo.ElementDecls[itemName] == null) {
                    schemaInfo.ElementDecls.Add(itemName, element.ElementDecl);
                }
            }
            foreach (XmlSchemaAttribute attribute in attributes.Values) {
                itemName = attribute.QualifiedName;
                schemaInfo.TargetNamespaces[itemName.Namespace] = true;
                if (schemaInfo.ElementDecls[itemName] == null) {
                    schemaInfo.AttributeDecls.Add(itemName, attribute.AttDef);
                }
            }
            foreach (XmlSchemaType type in types.Values) {
                itemName = type.QualifiedName;
                schemaInfo.TargetNamespaces[itemName.Namespace] = true;
                XmlSchemaComplexType complexType = type as XmlSchemaComplexType;
                if ((complexType == null || type != XmlSchemaComplexType.AnyType) && schemaInfo.ElementDeclsByType[itemName] == null) {
                    schemaInfo.ElementDeclsByType.Add(itemName, type.ElementDecl);
                }
            }
            foreach (XmlSchemaNotation notation in notations.Values) {
                itemName = notation.QualifiedName;
                schemaInfo.TargetNamespaces[itemName.Namespace] = true;
                SchemaNotation no = new SchemaNotation(itemName);
                no.SystemLiteral = notation.System;
                no.Pubid = notation.Public;
                if (schemaInfo.Notations[itemName.Name] == null) {
                    schemaInfo.Notations.Add(itemName.Name, no);
                }
            }
        }
#endif//TRUST_COMPILE_STATE
    }
}