File: System\Xml\Schema\XmlSchemaSet.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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Versioning;
using System.Threading;
 
namespace System.Xml.Schema
{
    /// <summary>
    /// The XmlSchemaSet contains a set of namespace URI's.
    /// Each namespace also have an associated private data cache
    /// corresponding to the XML-Data Schema or W3C XML Schema.
    /// The XmlSchemaSet will able to load only XSD schemas,
    /// and compile them into an internal "cooked schema representation".
    /// The Validate method then uses this internal representation for
    /// efficient runtime validation of any given subtree.
    /// </summary>
    public class XmlSchemaSet
    {
        private readonly XmlNameTable _nameTable;
        private SchemaNames? _schemaNames;
        private readonly SortedList _schemas;              // List of source schemas
 
        //Event handling
        private readonly ValidationEventHandler _internalEventHandler;
        private ValidationEventHandler? _eventHandler;
 
        private bool _isCompiled;
 
        //Dictionary<Uri, XmlSchema> schemaLocations;
        //Dictionary<ChameleonKey, XmlSchema> chameleonSchemas;
        private readonly Hashtable _schemaLocations;
        private readonly Hashtable _chameleonSchemas;
 
        private readonly Hashtable _targetNamespaces;
        private bool _compileAll;
 
        //Cached Compiled Info
        private SchemaInfo _cachedCompiledInfo;
 
        //Reader settings to parse schema
        private readonly XmlReaderSettings _readerSettings;
        private XmlSchema? _schemaForSchema;  //Only one schema for schema per set
 
        //Schema compilation settings
        private XmlSchemaCompilationSettings _compilationSettings;
 
        internal XmlSchemaObjectTable? elements;
        internal XmlSchemaObjectTable? attributes;
        internal XmlSchemaObjectTable? schemaTypes;
        internal XmlSchemaObjectTable? substitutionGroups;
        private XmlSchemaObjectTable? _typeExtensions;
 
        //Thread safety
        private object? _internalSyncObject;
        internal object InternalSyncObject
        {
            get
            {
                if (_internalSyncObject == null)
                {
                    object o = new object();
                    Interlocked.CompareExchange<object?>(ref _internalSyncObject, o, null);
                }
 
                return _internalSyncObject;
            }
        }
 
        //Constructors
 
        /// <summary>
        /// Construct a new empty schema schemas.
        /// </summary>
        public XmlSchemaSet() : this(new NameTable())
        {
        }
 
        /// <summary>
        /// Construct a new empty schema schemas with associated XmlNameTable.
        /// The XmlNameTable is used when loading schemas.
        /// </summary>
        public XmlSchemaSet(XmlNameTable nameTable)
        {
            ArgumentNullException.ThrowIfNull(nameTable);
 
            _nameTable = nameTable;
            _schemas = new SortedList();
 
            /*schemaLocations = new Dictionary<Uri, XmlSchema>();
            chameleonSchemas = new Dictionary<ChameleonKey, XmlSchema>();*/
            _schemaLocations = new Hashtable();
            _chameleonSchemas = new Hashtable();
            _targetNamespaces = new Hashtable();
            _internalEventHandler = new ValidationEventHandler(InternalValidationCallback);
            _eventHandler = _internalEventHandler;
 
            _readerSettings = new XmlReaderSettings();
 
            // we don't have to check XmlReaderSettings.EnableLegacyXmlSettings() here because the following
            // code will return same result either we are running on v4.5 or later
            if (_readerSettings.GetXmlResolver() == null)
            {
                // The created resolver will be used in the schema validation only
                _readerSettings.XmlResolver = XmlReaderSettings.GetDefaultPermissiveResolver();
                _readerSettings.IsXmlResolverSet = false;
            }
 
            _readerSettings.NameTable = nameTable;
            _readerSettings.DtdProcessing = DtdProcessing.Prohibit;
 
            _compilationSettings = new XmlSchemaCompilationSettings();
            _cachedCompiledInfo = new SchemaInfo();
            _compileAll = true;
        }
 
 
        //Public Properties
        /// <summary>
        /// The default XmlNameTable used by the XmlSchemaSet when loading new schemas.
        /// </summary>
        public XmlNameTable NameTable
        {
            get { return _nameTable; }
        }
 
        public event ValidationEventHandler ValidationEventHandler
        {
            add
            {
                _eventHandler -= _internalEventHandler;
                _eventHandler += value;
                _eventHandler ??= _internalEventHandler;
            }
            remove
            {
                _eventHandler -= value;
                _eventHandler ??= _internalEventHandler;
            }
        }
 
        /// <summary>
        /// IsCompiled is true when the schema set is in compiled state.
        /// </summary>
        public bool IsCompiled
        {
            get
            {
                return _isCompiled;
            }
        }
 
        public XmlResolver? XmlResolver
        {
            set
            {
                _readerSettings.XmlResolver = value;
            }
        }
 
        public XmlSchemaCompilationSettings CompilationSettings
        {
            get
            {
                return _compilationSettings;
            }
            set
            {
                _compilationSettings = value;
            }
        }
 
        /// <summary>
        /// Returns the count of schemas in the set.
        /// </summary>
        public int Count
        {
            get
            {
                return _schemas.Count;
            }
        }
 
        public XmlSchemaObjectTable GlobalElements => elements ??= new XmlSchemaObjectTable();
 
        public XmlSchemaObjectTable GlobalAttributes => attributes ??= new XmlSchemaObjectTable();
 
        public XmlSchemaObjectTable GlobalTypes => schemaTypes ??= new XmlSchemaObjectTable();
 
        internal XmlSchemaObjectTable SubstitutionGroups => substitutionGroups ??= new XmlSchemaObjectTable();
 
        /// <summary>
        /// Table of all types extensions
        /// </summary>
        internal Hashtable SchemaLocations
        {
            get
            {
                return _schemaLocations;
            }
        }
 
        /// <summary>
        /// Table of all types extensions
        /// </summary>
        internal XmlSchemaObjectTable TypeExtensions => _typeExtensions ??= new XmlSchemaObjectTable();
 
        //Public Methods
 
        /// <summary>
        /// Add the schema located by the given URL into the schema schemas.
        /// If the given schema references other namespaces, the schemas for those other
        /// namespaces are NOT automatically loaded.
        /// </summary>
        public XmlSchema? Add(string? targetNamespace, string schemaUri)
        {
            if (string.IsNullOrEmpty(schemaUri))
            {
                throw new ArgumentNullException(nameof(schemaUri));
            }
 
            if (targetNamespace != null)
            {
                targetNamespace = XmlComplianceUtil.CDataNormalize(targetNamespace);
            }
 
            XmlSchema? schema = null;
            lock (InternalSyncObject)
            {
                //Check if schema from url has already been added
                XmlResolver tempResolver = _readerSettings.GetXmlResolver() ?? XmlReaderSettings.GetDefaultPermissiveResolver();
                Uri tempSchemaUri = tempResolver.ResolveUri(null, schemaUri);
                if (IsSchemaLoaded(tempSchemaUri, targetNamespace, out schema))
                {
                    return schema;
                }
                else
                {
                    //Url already not processed; Load SOM from url
                    XmlReader reader = XmlReader.Create(schemaUri, _readerSettings);
                    try
                    {
                        schema = Add(targetNamespace, ParseSchema(targetNamespace, reader));
                        while (reader.Read()) ; // wellformness check;
                    }
                    finally
                    {
                        reader.Close();
                    }
                }
            }
 
            return schema;
        }
 
        /// <summary>
        /// Add the given schema into the schema schemas.
        /// If the given schema references other namespaces, the schemas for those
        /// other namespaces are NOT automatically loaded.
        /// </summary>
        public XmlSchema? Add(string? targetNamespace, XmlReader schemaDocument)
        {
            ArgumentNullException.ThrowIfNull(schemaDocument);
 
            if (targetNamespace != null)
            {
                targetNamespace = XmlComplianceUtil.CDataNormalize(targetNamespace);
            }
            lock (InternalSyncObject)
            {
                XmlSchema? schema = null;
                Uri schemaUri = new Uri(schemaDocument.BaseURI!, UriKind.RelativeOrAbsolute);
                if (IsSchemaLoaded(schemaUri, targetNamespace, out schema))
                {
                    return schema;
                }
                else
                {
                    DtdProcessing dtdProcessing = _readerSettings.DtdProcessing;
                    SetDtdProcessing(schemaDocument);
                    schema = Add(targetNamespace, ParseSchema(targetNamespace, schemaDocument));
                    _readerSettings.DtdProcessing = dtdProcessing; //reset dtdProcessing setting
                    return schema;
                }
            }
        }
 
 
        /// <summary>
        /// Adds all the namespaces defined in the given schemas
        /// (including their associated schemas) to this schemas.
        /// </summary>
        public void Add(XmlSchemaSet schemas)
        {
            ArgumentNullException.ThrowIfNull(schemas);
 
            if (this == schemas)
            {
                return;
            }
            bool thisLockObtained = false;
            bool schemasLockObtained = false;
            try
            {
                SpinWait spinner = default;
                while (true)
                {
                    Monitor.TryEnter(InternalSyncObject, ref thisLockObtained);
                    if (thisLockObtained)
                    {
                        Monitor.TryEnter(schemas.InternalSyncObject, ref schemasLockObtained);
                        if (schemasLockObtained)
                        {
                            break;
                        }
                        else
                        {
                            Monitor.Exit(InternalSyncObject); //Give up this lock and try both again
                            thisLockObtained = false;
                            spinner.SpinOnce(); //Let the thread that holds the lock run
                            continue;
                        }
                    }
                }
 
                XmlSchema? currentSchema;
                if (schemas.IsCompiled)
                {
                    CopyFromCompiledSet(schemas);
                }
                else
                {
                    bool remove = false;
                    string? tns = null;
                    foreach (XmlSchema? schema in schemas.SortedSchemas.Values)
                    {
                        tns = schema!.TargetNamespace ?? string.Empty;
                        if (_schemas.ContainsKey(schema.SchemaId) || FindSchemaByNSAndUrl(schema.BaseUri, tns, null) != null)
                        { //Do not already existing url
                            continue;
                        }
                        currentSchema = Add(schema.TargetNamespace, schema);
                        if (currentSchema == null)
                        {
                            remove = true;
                            break;
                        }
                    }
                    //Remove all from the set if even one schema in the passed in set is not preprocessed.
                    if (remove)
                    {
                        foreach (XmlSchema? schema in schemas.SortedSchemas.Values)
                        { //Remove all previously added schemas from the set
                            _schemas.Remove(schema!.SchemaId); //Might remove schema that was already there and was not added thru this operation
                            _schemaLocations.Remove(schema.BaseUri!);
                        }
                    }
                }
            }
            finally
            { //release locks on sets
                if (thisLockObtained)
                {
                    Monitor.Exit(InternalSyncObject);
                }
                if (schemasLockObtained)
                {
                    Monitor.Exit(schemas.InternalSyncObject);
                }
            }
        }
 
        public XmlSchema? Add(XmlSchema schema)
        {
            ArgumentNullException.ThrowIfNull(schema);
 
            lock (InternalSyncObject)
            {
                if (_schemas.ContainsKey(schema.SchemaId))
                {
                    return schema;
                }
 
                return Add(schema.TargetNamespace, schema);
            }
        }
 
        public XmlSchema? Remove(XmlSchema schema)
        {
            return Remove(schema, true);
        }
 
        public bool RemoveRecursive(XmlSchema schemaToRemove)
        {
            ArgumentNullException.ThrowIfNull(schemaToRemove);
 
            if (!_schemas.ContainsKey(schemaToRemove.SchemaId))
            {
                return false;
            }
 
            lock (InternalSyncObject)
            { //Need to lock here so that remove cannot be called while the set is being compiled
                if (_schemas.ContainsKey(schemaToRemove.SchemaId))
                { //Need to check again
                    //Build disallowedNamespaces list
                    Hashtable disallowedNamespaces = new Hashtable();
                    disallowedNamespaces.Add(GetTargetNamespace(schemaToRemove), schemaToRemove);
                    string importedNS;
                    for (int i = 0; i < schemaToRemove.ImportedNamespaces.Count; i++)
                    {
                        importedNS = (string)schemaToRemove.ImportedNamespaces[i]!;
                        if (disallowedNamespaces[importedNS] == null)
                        {
                            disallowedNamespaces.Add(importedNS, importedNS);
                        }
                    }
 
                    //Removal list is all schemas imported by this schema directly or indirectly
                    //Need to check if other schemas in the set import schemaToRemove / any of its imports
                    ArrayList needToCheckSchemaList = new ArrayList();
                    for (int i = 0; i < _schemas.Count; i++)
                    {
                        XmlSchema mainSchema = (XmlSchema)_schemas.GetByIndex(i)!;
                        if (mainSchema == schemaToRemove ||
                            schemaToRemove.ImportedSchemas.Contains(mainSchema))
                        {
                            continue;
                        }
                        needToCheckSchemaList.Add(mainSchema);
                    }
 
                    for (int i = 0; i < needToCheckSchemaList.Count; i++)
                    { //Perf: Not using nested foreach here
                        XmlSchema mainSchema = (XmlSchema)needToCheckSchemaList[i]!;
 
                        if (mainSchema.ImportedNamespaces.Count > 0)
                        {
                            foreach (string? tns in disallowedNamespaces.Keys)
                            {
                                if (mainSchema.ImportedNamespaces.Contains(tns))
                                {
                                    SendValidationEvent(new XmlSchemaException(SR.Sch_SchemaNotRemoved, string.Empty), XmlSeverityType.Warning);
                                    return false;
                                }
                            }
                        }
                    }
 
                    Remove(schemaToRemove, true);
                    for (int i = 0; i < schemaToRemove.ImportedSchemas.Count; ++i)
                    {
                        XmlSchema impSchema = (XmlSchema)schemaToRemove.ImportedSchemas[i]!;
                        Remove(impSchema, true);
                    }
                    return true;
                }
            }
            return false;
        }
 
        public bool Contains(string? targetNamespace)
        {
            targetNamespace ??= string.Empty;
 
            return _targetNamespaces[targetNamespace] != null;
        }
 
        public bool Contains(XmlSchema schema)
        {
            ArgumentNullException.ThrowIfNull(schema);
 
            return _schemas.ContainsValue(schema);
        }
 
        public void Compile()
        {
            if (_isCompiled)
            {
                return;
            }
            if (_schemas.Count == 0)
            {
                ClearTables(); //Clear any previously present compiled state left by calling just Remove() on the set
                _cachedCompiledInfo = new SchemaInfo();
                _isCompiled = true;
                _compileAll = false;
                return;
            }
            lock (InternalSyncObject)
            {
                if (!_isCompiled)
                { //Locking before checking isCompiled to avoid problems with double locking
                    Compiler compiler = new Compiler(_nameTable, _eventHandler, _schemaForSchema, _compilationSettings);
                    SchemaInfo newCompiledInfo = new SchemaInfo();
                    int schemaIndex = 0;
                    if (!_compileAll)
                    { //if we are not compiling everything again, Move the pre-compiled schemas to the compiler's tables
                        compiler.ImportAllCompiledSchemas(this);
                    }
                    try
                    { //First thing to do in the try block is to acquire locks since finally will try to release them.
                        //If we don't acquire the locks first, and an exception occurs in the code before the locking code, then Threading.SynchronizationLockException will be thrown
                        //when attempting to release it in the finally block
                        XmlSchema currentSchema;
                        XmlSchema xmlNSSchema = Preprocessor.GetBuildInSchema();
                        for (schemaIndex = 0; schemaIndex < _schemas.Count; schemaIndex++)
                        {
                            currentSchema = (XmlSchema)_schemas.GetByIndex(schemaIndex)!;
 
                            //Lock schema to be compiled
                            Monitor.Enter(currentSchema);
                            if (!currentSchema.IsPreprocessed)
                            {
                                SendValidationEvent(new XmlSchemaException(SR.Sch_SchemaNotPreprocessed, string.Empty), XmlSeverityType.Error);
                                _isCompiled = false;
                                return;
                            }
                            if (currentSchema.IsCompiledBySet)
                            {
                                if (!_compileAll)
                                {
                                    continue;
                                }
                                else if ((object)currentSchema == (object)xmlNSSchema)
                                { // prepare for xml namespace schema without cleanup
                                    compiler.Prepare(currentSchema, false);
                                    continue;
                                }
                            }
                            compiler.Prepare(currentSchema, true);
                        }
 
                        _isCompiled = compiler.Execute(this, newCompiledInfo);
                        if (_isCompiled)
                        {
                            if (!_compileAll)
                            {
                                newCompiledInfo.Add(_cachedCompiledInfo, _eventHandler); //Add all the items from the old to the new compiled object
                            }
                            _compileAll = false;
                            _cachedCompiledInfo = newCompiledInfo; //Replace the compiled info in the set after successful compilation
                        }
                    }
                    finally
                    {
                        //Release locks on all schemas
                        XmlSchema currentSchema;
                        if (schemaIndex == _schemas.Count)
                        {
                            schemaIndex--;
                        }
                        for (int i = schemaIndex; i >= 0; i--)
                        {
                            currentSchema = (XmlSchema)_schemas.GetByIndex(i)!;
                            if (currentSchema == Preprocessor.GetBuildInSchema())
                            { //dont re-set compiled flags for xml namespace schema
                                Monitor.Exit(currentSchema);
                                continue;
                            }
 
                            currentSchema.IsCompiledBySet = _isCompiled;
                            Monitor.Exit(currentSchema);
                        }
                    }
                }
            }
            return;
        }
 
        public XmlSchema Reprocess(XmlSchema schema)
        {
            ArgumentNullException.ThrowIfNull(schema);
 
            // Due to bug 644477 - this method is tightly coupled (THE CODE IS BASICALLY COPIED) to Remove, Add and AddSchemaToSet
            // methods. If you change anything here *make sure* to update Remove/Add/AddSchemaToSet method(s) accordingly.
            // The only difference is that we don't touch .schemas collection here to not break a code like this:
            // foreach (XmlSchema s in schemaset.schemas) { schemaset.Reprocess(s); }
            // This is by purpose.
            if (!_schemas.ContainsKey(schema.SchemaId))
            {
                throw new ArgumentException(SR.Sch_SchemaDoesNotExist, nameof(schema));
            }
            XmlSchema originalSchema = schema;
            lock (InternalSyncObject)
            { //Lock set so that set cannot be compiled in another thread
                // This code is copied from method:
                // Remove(XmlSchema schema, bool forceCompile)
                // If you changed anything here go and change the same in Remove(XmlSchema schema, bool forceCompile) method
                #region Copied from Remove(XmlSchema schema, bool forceCompile)
 
                RemoveSchemaFromGlobalTables(schema);
                RemoveSchemaFromCaches(schema);
                if (schema.BaseUri != null)
                {
                    _schemaLocations.Remove(schema.BaseUri);
                }
                string tns = GetTargetNamespace(schema);
                if (Schemas(tns).Count == 0)
                { //This is the only schema for that namespace
                    _targetNamespaces.Remove(tns);
                }
                _isCompiled = false;
                _compileAll = true; //Force compilation of the whole set; This is when the set is not completely thread-safe
 
                #endregion //Copied from Remove(XmlSchema schema, bool forceCompile)
 
 
                // This code is copied from method:
                // Add(string targetNamespace, XmlSchema schema)
                // If you changed anything here go and change the same in Add(string targetNamespace, XmlSchema schema) method
                #region Copied from Add(string targetNamespace, XmlSchema schema)
 
                if (schema.ErrorCount != 0)
                { //Schema with parsing errors cannot be loaded
                    return originalSchema;
                }
                if (PreprocessSchema(ref schema, schema.TargetNamespace))
                { //No perf opt for already compiled schemas
                    // This code is copied from method:
                    // AddSchemaToSet(XmlSchema schema)
                    // If you changed anything here go and change the same in AddSchemaToSet(XmlSchema schema) method
                    #region Copied from AddSchemaToSet(XmlSchema schema)
 
                    //Add to targetNamespaces table
                    if (_targetNamespaces[tns] == null)
                    {
                        _targetNamespaces.Add(tns, tns);
                    }
                    if (_schemaForSchema == null && tns == XmlReservedNs.NsXs && schema.SchemaTypes[DatatypeImplementation.QnAnyType] != null)
                    { //it has xs:anyType
                        _schemaForSchema = schema;
                    }
                    for (int i = 0; i < schema.ImportedSchemas.Count; ++i)
                    {    //Once preprocessed external schemas property is set
                        XmlSchema s = (XmlSchema)schema.ImportedSchemas[i]!;
                        if (!_schemas.ContainsKey(s.SchemaId))
                        {
                            _schemas.Add(s.SchemaId, s);
                        }
                        tns = GetTargetNamespace(s);
                        if (_targetNamespaces[tns] == null)
                        {
                            _targetNamespaces.Add(tns, tns);
                        }
                        if (_schemaForSchema == null && tns == XmlReservedNs.NsXs && schema.SchemaTypes[DatatypeImplementation.QnAnyType] != null)
                        { //it has xs:anyType
                            _schemaForSchema = schema;
                        }
                    }
                    #endregion //Copied from AddSchemaToSet(XmlSchema schema)
                    return schema;
                }
                #endregion // Copied from Add(string targetNamespace, XmlSchema schema)
 
                return originalSchema;
            }
        }
 
        public void CopyTo(XmlSchema[] schemas, int index)
        {
            ArgumentNullException.ThrowIfNull(schemas);
 
            ArgumentOutOfRangeException.ThrowIfNegative(index);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(index, schemas.Length - 1);
 
            _schemas.Values.CopyTo(schemas, index);
        }
 
        public ICollection Schemas()
        {
            return _schemas.Values;
        }
 
        public ICollection Schemas(string? targetNamespace)
        {
            ArrayList tnsSchemas = new ArrayList();
            XmlSchema currentSchema;
            targetNamespace ??= string.Empty;
            for (int i = 0; i < _schemas.Count; i++)
            {
                currentSchema = (XmlSchema)_schemas.GetByIndex(i)!;
                if (GetTargetNamespace(currentSchema) == targetNamespace)
                {
                    tnsSchemas.Add(currentSchema);
                }
            }
            return tnsSchemas;
        }
 
        //Internal Methods
 
        private XmlSchema? Add(string? targetNamespace, XmlSchema? schema)
        {
            // Due to bug 644477 - this method is tightly coupled (THE CODE IS BASICALLY COPIED) to Reprocess
            // method. If you change anything here *make sure* to update Reprocess method accordingly.
 
            if (schema == null || schema.ErrorCount != 0)
            { //Schema with parsing errors cannot be loaded
                return null;
            }
 
            // This code is copied to method:
            // Reprocess(XmlSchema schema)
            // If you changed anything here go and change the same in Reprocess(XmlSchema schema) method
            if (PreprocessSchema(ref schema, targetNamespace))
            { //No perf opt for already compiled schemas
                AddSchemaToSet(schema);
                _isCompiled = false;
                return schema;
            }
 
            return null;
        }
 
#if TRUST_COMPILE_STATE
        private void AddCompiledSchema(XmlSchema schema) {
            if (schema.IsCompiledBySet ) { //trust compiled state always if it is not a chameleon schema
                VerifyTables();
                SchemaInfo newCompiledInfo = new SchemaInfo();
                XmlSchemaObjectTable substitutionGroupsTable = null;
                if (!AddToCompiledInfo(schema, newCompiledInfo, ref substitutionGroupsTable)) { //Error while adding main schema
                    return null;
                }
                foreach (XmlSchema impSchema in schema.ImportedSchemas) {
                    if (!AddToCompiledInfo(impSchema, newCompiledInfo, ref substitutionGroupsTable)) { //Error while adding imports
                        return null;
                    }
                }
                newCompiledInfo.Add(cachedCompiledInfo, eventHandler); //Add existing compiled info
                cachedCompiledInfo = newCompiledInfo;
                if (substitutionGroupsTable != null) {
                    ProcessNewSubstitutionGroups(substitutionGroupsTable, true);
                }
                if (schemas.Count == 0) { //If its the first compiled schema being added, then set doesnt need to be compiled
                    isCompiled = true;
                    compileAll = false;
                }
                AddSchemaToSet(schema);
                return schema;
            }
        }
 
        private bool AddToCompiledInfo(XmlSchema schema, SchemaInfo newCompiledInfo, ref XmlSchemaObjectTable substTable) {
            //Add schema's compiled tables to the set
            if (schema.BaseUri != null && schemaLocations[schema.BaseUri] == null) { //Update schemaLocations table
                schemaLocations.Add(schema.BaseUri, schema);
            }
 
            foreach (XmlSchemaElement element in schema.Elements.Values) {
                if (!AddToTable(elements, element.QualifiedName, element)) {
                    RemoveSchemaFromGlobalTables(schema);
                    return false;
                }
                XmlQualifiedName head = element.SubstitutionGroup;
                if (!head.IsEmpty) {
                    substTable ??= new XmlSchemaObjectTable();
                    XmlSchemaSubstitutionGroup substitutionGroup = (XmlSchemaSubstitutionGroup)substTable[head];
                    if (substitutionGroup == null) {
                        substitutionGroup = new XmlSchemaSubstitutionGroup();
                        substitutionGroup.Examplar = head;
                        substTable.Add(head, substitutionGroup);
                    }
                    ArrayList members = substitutionGroup.Members;
                    if (!members.Contains(element)) { //Members might contain element if the same schema is included and imported through different paths. Imp, hence will be added to set directly
                        members.Add(element);
                    }
                }
            }
            foreach (XmlSchemaAttribute attribute in schema.Attributes.Values) {
                if (!AddToTable(attributes, attribute.QualifiedName, attribute)) {
                    RemoveSchemaFromGlobalTables(schema);
                    return false;
                }
            }
            foreach (XmlSchemaType schemaType in schema.SchemaTypes.Values) {
                if (!AddToTable(schemaTypes, schemaType.QualifiedName, schemaType)) {
                    RemoveSchemaFromGlobalTables(schema);
                    return false;
                }
            }
            schema.AddCompiledInfo(newCompiledInfo);
 
            return true;
        }
#endif
 
        //For use by the validator when loading schemaLocations in the instance
        internal void Add(string? targetNamespace, XmlReader reader, Hashtable validatedNamespaces)
        {
            ArgumentNullException.ThrowIfNull(reader);
 
            targetNamespace ??= string.Empty;
            if (validatedNamespaces[targetNamespace] != null)
            {
                if (FindSchemaByNSAndUrl(new Uri(reader.BaseURI!, UriKind.RelativeOrAbsolute), targetNamespace, null) != null)
                {
                    return;
                }
                else
                {
                    throw new XmlSchemaException(SR.Sch_ComponentAlreadySeenForNS, targetNamespace);
                }
            }
 
            //Not locking set as this will not be accessible outside the validator
            XmlSchema? schema;
            if (IsSchemaLoaded(new Uri(reader.BaseURI!, UriKind.RelativeOrAbsolute), targetNamespace, out _))
            {
                return;
            }
            else
            { //top-level schema not present for same url
                schema = ParseSchema(targetNamespace, reader);
 
                //Store the previous locations
                DictionaryEntry[] oldLocations = new DictionaryEntry[_schemaLocations.Count];
                _schemaLocations.CopyTo(oldLocations, 0);
 
                //Add to set
                Add(targetNamespace, schema!);
                if (schema!.ImportedSchemas.Count > 0)
                { //Check imports
                    string? tns;
                    for (int i = 0; i < schema.ImportedSchemas.Count; ++i)
                    {
                        XmlSchema impSchema = (XmlSchema)schema.ImportedSchemas[i]!;
                        tns = impSchema.TargetNamespace ?? string.Empty;
                        if (validatedNamespaces[tns] != null && (FindSchemaByNSAndUrl(impSchema.BaseUri, tns, oldLocations) == null))
                        {
                            RemoveRecursive(schema);
                            throw new XmlSchemaException(SR.Sch_ComponentAlreadySeenForNS, tns);
                        }
                    }
                }
            }
        }
 
        internal XmlSchema? FindSchemaByNSAndUrl(Uri? schemaUri, string ns, DictionaryEntry[]? locationsTable)
        {
            if (schemaUri == null || schemaUri.OriginalString.Length == 0)
            {
                return null;
            }
 
            XmlSchema? schema = null;
            if (locationsTable == null)
            {
                schema = (XmlSchema)_schemaLocations[schemaUri]!;
            }
            else
            {
                for (int i = 0; i < locationsTable.Length; i++)
                {
                    if (schemaUri.Equals(locationsTable[i].Key))
                    {
                        schema = (XmlSchema)locationsTable[i].Value!;
                        break;
                    }
                }
            }
 
            if (schema != null)
            {
                Debug.Assert(ns != null);
                string tns = schema.TargetNamespace ?? string.Empty;
                if (tns == ns)
                {
                    return schema;
                }
                else if (tns.Length == 0)
                { //There could be a chameleon for same ns
                    // It is OK to pass in the schema we have found so far, since it must have the schemaUri we're looking for
                    // (we found it that way above) and it must be the original chameleon schema (the one without target ns)
                    // as we don't add the chameleon copies into the locations tables above.
                    Debug.Assert(schema.BaseUri!.Equals(schemaUri));
                    ChameleonKey cKey = new ChameleonKey(ns, schema);
                    schema = (XmlSchema)_chameleonSchemas[cKey]!; //Need not clone if a schema for that namespace already exists
                }
                else
                {
                    schema = null;
                }
            }
 
            return schema;
        }
 
        private void SetDtdProcessing(XmlReader reader)
        {
            if (reader.Settings != null)
            {
                _readerSettings.DtdProcessing = reader.Settings.DtdProcessing;
            }
            else
            {
                XmlTextReader? v1Reader = reader as XmlTextReader;
                if (v1Reader != null)
                {
                    _readerSettings.DtdProcessing = v1Reader.DtdProcessing;
                }
            }
        }
 
        private void AddSchemaToSet(XmlSchema schema)
        {
            // Due to bug 644477 - this method is tightly coupled (THE CODE IS BASICALLY COPIED) to Reprocess
            // method. If you change anything here *make sure* to update Reprocess method accordingly.
 
            _schemas.Add(schema.SchemaId, schema);
            //Add to targetNamespaces table
 
            // This code is copied to method:
            // Reprocess(XmlSchema schema)
            // If you changed anything here go and change the same in Reprocess(XmlSchema schema) method
            #region This code is copied to Reprocess(XmlSchema schema) method
 
            string tns = GetTargetNamespace(schema);
            if (_targetNamespaces[tns] == null)
            {
                _targetNamespaces.Add(tns, tns);
            }
            if (_schemaForSchema == null && tns == XmlReservedNs.NsXs && schema.SchemaTypes[DatatypeImplementation.QnAnyType] != null)
            { //it has xs:anyType
                _schemaForSchema = schema;
            }
            for (int i = 0; i < schema.ImportedSchemas.Count; ++i)
            {    //Once preprocessed external schemas property is set
                XmlSchema s = (XmlSchema)schema.ImportedSchemas[i]!;
                if (!_schemas.ContainsKey(s.SchemaId))
                {
                    _schemas.Add(s.SchemaId, s);
                }
                tns = GetTargetNamespace(s);
                if (_targetNamespaces[tns] == null)
                {
                    _targetNamespaces.Add(tns, tns);
                }
                if (_schemaForSchema == null && tns == XmlReservedNs.NsXs && schema.SchemaTypes[DatatypeImplementation.QnAnyType] != null)
                { //it has xs:anyType
                    _schemaForSchema = schema;
                }
            }
 
            #endregion // This code is copied to Reprocess(XmlSchema schema) method
        }
 
        private void ProcessNewSubstitutionGroups(XmlSchemaObjectTable substitutionGroupsTable, bool resolve)
        {
            foreach (XmlSchemaSubstitutionGroup? substGroup in substitutionGroupsTable.Values)
            {
                if (resolve)
                { //Resolve substitutionGroups within this schema
                    ResolveSubstitutionGroup(substGroup!, substitutionGroupsTable);
                }
 
                //Add or Merge new substitutionGroups with those that already exist in the set
                XmlQualifiedName head = substGroup!.Examplar;
                XmlSchemaSubstitutionGroup? oldSubstGroup = (XmlSchemaSubstitutionGroup?)substitutionGroups![head];
                if (oldSubstGroup != null)
                {
                    for (int i = 0; i < substGroup.Members.Count; ++i)
                    {
                        if (!oldSubstGroup.Members.Contains(substGroup.Members[i]))
                        {
                            oldSubstGroup.Members.Add(substGroup.Members[i]);
                        }
                    }
                }
                else
                {
                    AddToTable(substitutionGroups, head, substGroup);
                }
            }
        }
 
        private void ResolveSubstitutionGroup(XmlSchemaSubstitutionGroup substitutionGroup, XmlSchemaObjectTable substTable)
        {
            List<XmlSchemaElement>? newMembers = null;
            XmlSchemaElement headElement = (XmlSchemaElement)elements![substitutionGroup.Examplar]!;
            if (substitutionGroup.Members.Contains(headElement))
            {// already checked
                return;
            }
            for (int i = 0; i < substitutionGroup.Members.Count; ++i)
            {
                XmlSchemaElement element = (XmlSchemaElement)substitutionGroup.Members[i]!;
 
                //Chain to other head's that are members of this head's substGroup
                XmlSchemaSubstitutionGroup? g = (XmlSchemaSubstitutionGroup?)substTable[element.QualifiedName];
                if (g != null)
                {
                    ResolveSubstitutionGroup(g, substTable);
                    for (int j = 0; j < g.Members.Count; ++j)
                    {
                        XmlSchemaElement element1 = (XmlSchemaElement)g.Members[j]!;
                        if (element1 != element)
                        { //Exclude the head
                            newMembers ??= new List<XmlSchemaElement>();
                            newMembers.Add(element1);
                        }
                    }
                }
            }
            if (newMembers != null)
            {
                for (int i = 0; i < newMembers.Count; ++i)
                {
                    substitutionGroup.Members.Add(newMembers[i]);
                }
            }
            substitutionGroup.Members.Add(headElement);
        }
 
        internal XmlSchema? Remove(XmlSchema schema, bool forceCompile)
        {
            ArgumentNullException.ThrowIfNull(schema);
 
            // Due to bug 644477 - this method is tightly coupled (THE CODE IS BASICALLY COPIED) to Reprocess
            // method. If you change anything here *make sure* to update Reprocess method accordingly.
            lock (InternalSyncObject)
            { //Need to lock here so that remove cannot be called while the set is being compiled
                if (_schemas.ContainsKey(schema.SchemaId))
                {
                    // This code is copied to method:
                    // Reprocess(XmlSchema schema)
                    // If you changed anything here go and change the same in Reprocess(XmlSchema schema) method
                    #region This code is copied to Reprocess(XmlSchema schema) method
 
                    if (forceCompile)
                    {
                        RemoveSchemaFromGlobalTables(schema);
                        RemoveSchemaFromCaches(schema);
                    }
                    _schemas.Remove(schema.SchemaId);
                    if (schema.BaseUri != null)
                    {
                        _schemaLocations.Remove(schema.BaseUri);
                    }
                    string tns = GetTargetNamespace(schema);
                    if (Schemas(tns).Count == 0)
                    { //This is the only schema for that namespace
                        _targetNamespaces.Remove(tns);
                    }
                    if (forceCompile)
                    {
                        _isCompiled = false;
                        _compileAll = true; //Force compilation of the whole set; This is when the set is not completely thread-safe
                    }
                    return schema;
 
                    #endregion // This code is copied to Reprocess(XmlSchema schema) method
                }
            }
 
            return null;
        }
 
        private void ClearTables()
        {
            GlobalElements.Clear();
            GlobalAttributes.Clear();
            GlobalTypes.Clear();
            SubstitutionGroups.Clear();
            TypeExtensions.Clear();
        }
 
        internal bool PreprocessSchema(ref XmlSchema schema, string? targetNamespace)
        {
            Preprocessor prep = new Preprocessor(_nameTable, GetSchemaNames(_nameTable), _eventHandler, _compilationSettings);
            prep.XmlResolver = _readerSettings.GetXmlResolver_CheckConfig();
            prep.ReaderSettings = _readerSettings;
            prep.SchemaLocations = _schemaLocations;
            prep.ChameleonSchemas = _chameleonSchemas;
            bool hasErrors = prep.Execute(schema, targetNamespace, true);
            schema = prep.RootSchema!; //For any root level chameleon cloned
            return hasErrors;
        }
 
        internal XmlSchema? ParseSchema(string? targetNamespace, XmlReader reader)
        {
            XmlNameTable readerNameTable = reader.NameTable;
            SchemaNames schemaNames = GetSchemaNames(readerNameTable);
            Parser parser = new Parser(SchemaType.XSD, readerNameTable, schemaNames, _eventHandler);
            parser.XmlResolver = _readerSettings.GetXmlResolver_CheckConfig();
            try
            {
                parser.Parse(reader, targetNamespace);
            }
            catch (XmlSchemaException e)
            {
                SendValidationEvent(e, XmlSeverityType.Error);
                return null;
            }
            return parser.XmlSchema;
        }
 
        internal void CopyFromCompiledSet(XmlSchemaSet otherSet)
        {
            XmlSchema currentSchema;
            SortedList copyFromList = otherSet.SortedSchemas;
            bool setIsCompiled = _schemas.Count == 0 ? true : false;
            ArrayList existingSchemas = new ArrayList();
 
            SchemaInfo newCompiledInfo = new SchemaInfo();
            Uri? baseUri;
            for (int i = 0; i < copyFromList.Count; i++)
            {
                currentSchema = (XmlSchema)copyFromList.GetByIndex(i)!;
                baseUri = currentSchema.BaseUri;
                if (_schemas.ContainsKey(currentSchema.SchemaId) || (baseUri != null && baseUri.OriginalString.Length != 0 && _schemaLocations[baseUri] != null))
                {
                    existingSchemas.Add(currentSchema);
                    continue;
                }
                _schemas.Add(currentSchema.SchemaId, currentSchema);
                if (baseUri != null && baseUri.OriginalString.Length != 0)
                {
                    _schemaLocations.Add(baseUri, currentSchema);
                }
                string tns = GetTargetNamespace(currentSchema);
                if (_targetNamespaces[tns] == null)
                {
                    _targetNamespaces.Add(tns, tns);
                }
            }
 
            VerifyTables();
            foreach (XmlSchemaElement? element in otherSet.GlobalElements.Values)
            {
                if (!AddToTable(elements!, element!.QualifiedName, element))
                {
                    goto RemoveAll;
                }
            }
            foreach (XmlSchemaAttribute? attribute in otherSet.GlobalAttributes.Values)
            {
                if (!AddToTable(attributes!, attribute!.QualifiedName, attribute))
                {
                    goto RemoveAll;
                }
            }
            foreach (XmlSchemaType? schemaType in otherSet.GlobalTypes.Values)
            {
                if (!AddToTable(schemaTypes!, schemaType!.QualifiedName, schemaType))
                {
                    goto RemoveAll;
                }
            }
            ProcessNewSubstitutionGroups(otherSet.SubstitutionGroups, false);
 
            newCompiledInfo.Add(_cachedCompiledInfo, _eventHandler); //Add all the items from the old to the new compiled object
            newCompiledInfo.Add(otherSet.CompiledInfo, _eventHandler);
            _cachedCompiledInfo = newCompiledInfo; //Replace the compiled info in the set after successful compilation
            if (setIsCompiled)
            {
                _isCompiled = true;
                _compileAll = false;
            }
            return;
 
        RemoveAll:
            foreach (XmlSchema? schemaToRemove in copyFromList.Values)
            {
                if (!existingSchemas.Contains(schemaToRemove))
                {
                    Remove(schemaToRemove!, false);
                }
            }
 
            foreach (XmlSchemaElement? elementToRemove in otherSet.GlobalElements.Values)
            {
                if (!existingSchemas.Contains((XmlSchema?)elementToRemove!.Parent))
                {
                    elements!.Remove(elementToRemove.QualifiedName);
                }
            }
 
            foreach (XmlSchemaAttribute? attributeToRemove in otherSet.GlobalAttributes.Values)
            {
                if (!existingSchemas.Contains((XmlSchema?)attributeToRemove!.Parent))
                {
                    attributes!.Remove(attributeToRemove.QualifiedName);
                }
            }
 
            foreach (XmlSchemaType? schemaTypeToRemove in otherSet.GlobalTypes.Values)
            {
                if (!existingSchemas.Contains((XmlSchema?)schemaTypeToRemove!.Parent))
                {
                    schemaTypes!.Remove(schemaTypeToRemove.QualifiedName);
                }
            }
        }
 
        internal SchemaInfo CompiledInfo
        {
            get
            {
                return _cachedCompiledInfo;
            }
        }
 
        internal XmlReaderSettings ReaderSettings
        {
            get
            {
                return _readerSettings;
            }
        }
 
        internal XmlResolver? GetResolver()
        {
            return _readerSettings.GetXmlResolver_CheckConfig();
        }
 
        internal ValidationEventHandler? GetEventHandler()
        {
            return _eventHandler;
        }
 
        internal SchemaNames GetSchemaNames(XmlNameTable nt)
        {
            if (_nameTable != nt)
            {
                return new SchemaNames(nt);
            }
            else
            {
                return _schemaNames ??= new SchemaNames(_nameTable);
            }
        }
 
        internal bool IsSchemaLoaded(Uri schemaUri, string? targetNamespace, out XmlSchema? schema)
        {
            targetNamespace ??= string.Empty;
            if (GetSchemaByUri(schemaUri, out schema))
            {
                if (_schemas.ContainsKey(schema.SchemaId) && (targetNamespace.Length == 0 || targetNamespace == schema.TargetNamespace))
                { //schema is present in set
                    //Schema found
                }
                else if (schema.TargetNamespace == null)
                { //If schema not in set or namespace doesnt match, then it might be a chameleon
                    XmlSchema? chameleonSchema = FindSchemaByNSAndUrl(schemaUri, targetNamespace, null);
                    if (chameleonSchema != null && _schemas.ContainsKey(chameleonSchema.SchemaId))
                    {
                        schema = chameleonSchema;
                    }
                    else
                    {
                        schema = Add(targetNamespace, schema);
                    }
                }
                else if (targetNamespace.Length != 0 && targetNamespace != schema.TargetNamespace)
                {
                    SendValidationEvent(new XmlSchemaException(SR.Sch_MismatchTargetNamespaceEx, new string[] { targetNamespace, schema.TargetNamespace }), XmlSeverityType.Error);
                    schema = null;
                }
                else
                {
                    //If here, schema not present in set but in loc and might be added in loc through an earlier include
                    //S.TNS != null && ( tns == null or tns == s.TNS)
                    AddSchemaToSet(schema);
                }
 
                return true; //Schema Found
            }
 
            return false;
        }
 
        internal bool GetSchemaByUri(Uri schemaUri, [NotNullWhen(true)] out XmlSchema? schema)
        {
            schema = null;
            if (schemaUri == null || schemaUri.OriginalString.Length == 0)
            {
                return false;
            }
 
            schema = (XmlSchema?)_schemaLocations[schemaUri];
            if (schema != null)
            {
                return true;
            }
 
            return false;
        }
 
        internal static string GetTargetNamespace(XmlSchema schema)
        {
            return schema.TargetNamespace ?? string.Empty;
        }
 
        internal SortedList SortedSchemas
        {
            get
            {
                return _schemas;
            }
        }
 
        //Private Methods
        private void RemoveSchemaFromCaches(XmlSchema schema)
        {
            //Remove From ChameleonSchemas and schemaLocations cache
            List<XmlSchema> reprocessList = new List<XmlSchema>();
            XmlSchema.GetExternalSchemasList(reprocessList, schema);
            for (int i = 0; i < reprocessList.Count; ++i)
            { //Remove schema from schemaLocations & chameleonSchemas tables
                if (reprocessList[i].BaseUri != null && reprocessList[i].BaseUri!.OriginalString.Length != 0)
                {
                    _schemaLocations.Remove(reprocessList[i].BaseUri!);
                }
 
                //Remove from chameleon table
                ICollection chameleonKeys = _chameleonSchemas.Keys;
                ArrayList removalList = new ArrayList();
                foreach (ChameleonKey? cKey in chameleonKeys)
                {
                    if (cKey!.chameleonLocation.Equals(reprocessList[i].BaseUri))
                    {
                        // The key will have the originalSchema set to null if the location was not empty
                        //   otherwise we need to care about it as there may be more chameleon schemas without
                        //   a base URI and we want to remove only those which were created as a clone of the one
                        //   we're removing.
                        if (cKey.originalSchema == null || Ref.ReferenceEquals(cKey.originalSchema, reprocessList[i]))
                        {
                            removalList.Add(cKey);
                        }
                    }
                }
 
                for (int j = 0; j < removalList.Count; ++j)
                {
                    _chameleonSchemas.Remove(removalList[j]!);
                }
            }
        }
 
        private void RemoveSchemaFromGlobalTables(XmlSchema schema)
        {
            if (_schemas.Count == 0)
            {
                return;
            }
 
            VerifyTables();
            foreach (XmlSchemaElement? elementToRemove in schema.Elements.Values)
            {
                XmlSchemaElement? elem = (XmlSchemaElement?)elements![elementToRemove!.QualifiedName];
                if (elem == elementToRemove)
                {
                    elements.Remove(elementToRemove.QualifiedName);
                }
            }
 
            foreach (XmlSchemaAttribute? attributeToRemove in schema.Attributes.Values)
            {
                XmlSchemaAttribute? attr = (XmlSchemaAttribute?)attributes![attributeToRemove!.QualifiedName];
                if (attr == attributeToRemove)
                {
                    attributes.Remove(attributeToRemove.QualifiedName);
                }
            }
 
            foreach (XmlSchemaType? schemaTypeToRemove in schema.SchemaTypes.Values)
            {
                XmlSchemaType? schemaType = (XmlSchemaType?)schemaTypes![schemaTypeToRemove!.QualifiedName];
                if (schemaType == schemaTypeToRemove)
                {
                    schemaTypes.Remove(schemaTypeToRemove.QualifiedName);
                }
            }
        }
 
        private bool AddToTable(XmlSchemaObjectTable table, XmlQualifiedName qname, XmlSchemaObject item)
        {
            if (qname.Name.Length == 0)
            {
                return true;
            }
 
            XmlSchemaObject? existingObject = (XmlSchemaObject?)table[qname];
            if (existingObject != null)
            {
                if (existingObject == item || existingObject.SourceUri == item.SourceUri)
                {
                    return true;
                }
 
                string code = string.Empty;
                if (item is XmlSchemaComplexType)
                {
                    code = SR.Sch_DupComplexType;
                }
                else if (item is XmlSchemaSimpleType)
                {
                    code = SR.Sch_DupSimpleType;
                }
                else if (item is XmlSchemaElement)
                {
                    code = SR.Sch_DupGlobalElement;
                }
                else if (item is XmlSchemaAttribute)
                {
                    if (qname.Namespace == XmlReservedNs.NsXml)
                    {
                        XmlSchema schemaForXmlNS = Preprocessor.GetBuildInSchema();
                        XmlSchemaObject builtInAttribute = schemaForXmlNS.Attributes[qname]!;
                        if (existingObject == builtInAttribute)
                        { //replace built-in one
                            table.Insert(qname, item);
                            return true;
                        }
                        else if (item == builtInAttribute)
                        { //trying to overwrite customer's component with built-in, ignore built-in
                            return true;
                        }
                    }
 
                    code = SR.Sch_DupGlobalAttribute;
                }
 
                SendValidationEvent(new XmlSchemaException(code, qname.ToString()), XmlSeverityType.Error);
                return false;
            }
            else
            {
                table.Add(qname, item);
                return true;
            }
        }
 
        private void VerifyTables()
        {
            elements ??= new XmlSchemaObjectTable();
            attributes ??= new XmlSchemaObjectTable();
            schemaTypes ??= new XmlSchemaObjectTable();
            substitutionGroups ??= new XmlSchemaObjectTable();
        }
 
        private void InternalValidationCallback(object? sender, ValidationEventArgs e)
        {
            if (e.Severity == XmlSeverityType.Error)
            {
                throw e.Exception;
            }
        }
 
        private void SendValidationEvent(XmlSchemaException e, XmlSeverityType severity)
        {
            if (_eventHandler != null)
            {
                _eventHandler(this, new ValidationEventArgs(e, severity));
            }
            else
            {
                throw e;
            }
        }
    };
}