File: System\Xml\Schema\XsdValidator.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.Specialized;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.Versioning;
using System.Text;
using System.Xml.Schema;
using System.Xml.XPath;
 
namespace System.Xml.Schema
{
#pragma warning disable 618
    internal sealed class XsdValidator : BaseValidator
    {
        private int _startIDConstraint = -1;
        private const int STACK_INCREMENT = 10;
        private HWStack _validationStack;  // validaton contexts
 
        private Hashtable _attPresence;
        private XmlNamespaceManager _nsManager;
        private bool _bManageNamespaces;
        private Hashtable? _IDs;
        private IdRefNode? _idRefListHead;
        private Parser? _inlineSchemaParser;
        private XmlSchemaContentProcessing _processContents;
 
        private static readonly XmlSchemaDatatype s_dtCDATA = XmlSchemaDatatype.FromXmlTokenizedType(XmlTokenizedType.CDATA)!;
        private static readonly XmlSchemaDatatype s_dtQName = XmlSchemaDatatype.FromXmlTokenizedTypeXsd(XmlTokenizedType.QName)!;
        private static readonly XmlSchemaDatatype s_dtStringArray = s_dtCDATA.DeriveByList(null);
 
        //To avoid SchemaNames creation
        private string _nsXmlNs;
        private string _nsXs;
        private string _nsXsi;
        private string _xsiType;
        private string _xsiNil;
        private string _xsiSchemaLocation;
        private string _xsiNoNamespaceSchemaLocation;
        private string _xsdSchema;
 
 
        internal XsdValidator(BaseValidator validator) : base(validator)
        {
            Init();
        }
 
        internal XsdValidator(XmlValidatingReaderImpl reader, XmlSchemaCollection schemaCollection, IValidationEventHandling eventHandling) : base(reader, schemaCollection, eventHandling)
        {
            Init();
        }
 
        [MemberNotNull(nameof(_nsManager))]
        [MemberNotNull(nameof(_validationStack))]
        [MemberNotNull(nameof(_attPresence))]
        [MemberNotNull(nameof(_processContents))]
        [MemberNotNull(nameof(_nsXmlNs))]
        [MemberNotNull(nameof(_nsXs))]
        [MemberNotNull(nameof(_nsXsi))]
        [MemberNotNull(nameof(_xsiType))]
        [MemberNotNull(nameof(_xsiNil))]
        [MemberNotNull(nameof(_xsiSchemaLocation))]
        [MemberNotNull(nameof(_xsiNoNamespaceSchemaLocation))]
        [MemberNotNull(nameof(_xsdSchema))]
        private void Init()
        {
            _nsManager = reader.NamespaceManager!;
            if (_nsManager == null)
            {
                _nsManager = new XmlNamespaceManager(NameTable);
                _bManageNamespaces = true;
            }
 
            _validationStack = new HWStack(STACK_INCREMENT);
            textValue = new StringBuilder();
            _attPresence = new Hashtable();
            schemaInfo = new SchemaInfo();
            checkDatatype = false;
            _processContents = XmlSchemaContentProcessing.Strict;
            Push(XmlQualifiedName.Empty);
 
            //Add common strings to be compared to NameTable
            _nsXmlNs = NameTable.Add(XmlReservedNs.NsXmlNs);
            _nsXs = NameTable.Add(XmlReservedNs.NsXs);
            _nsXsi = NameTable.Add(XmlReservedNs.NsXsi);
            _xsiType = NameTable.Add("type");
            _xsiNil = NameTable.Add("nil");
            _xsiSchemaLocation = NameTable.Add("schemaLocation");
            _xsiNoNamespaceSchemaLocation = NameTable.Add("noNamespaceSchemaLocation");
            _xsdSchema = NameTable.Add("schema");
        }
 
        public override void Validate()
        {
            if (IsInlineSchemaStarted)
            {
                ProcessInlineSchema();
            }
            else
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        ValidateElement();
                        if (reader.IsEmptyElement)
                        {
                            goto case XmlNodeType.EndElement;
                        }
                        break;
                    case XmlNodeType.Whitespace:
                        ValidateWhitespace();
                        break;
                    case XmlNodeType.Text:          // text inside a node
                    case XmlNodeType.CDATA:         // <![CDATA[...]]>
                    case XmlNodeType.SignificantWhitespace:
                        ValidateText();
                        break;
                    case XmlNodeType.EndElement:
                        ValidateEndElement();
                        break;
                }
            }
        }
 
 
        public override void CompleteValidation()
        {
            CheckForwardRefs();
        }
 
        private bool IsInlineSchemaStarted
        {
            get { return _inlineSchemaParser != null; }
        }
 
        private void ProcessInlineSchema()
        {
            if (!_inlineSchemaParser!.ParseReaderNode())
            { // Done
                _inlineSchemaParser.FinishParsing();
                XmlSchema? schema = _inlineSchemaParser.XmlSchema;
                string? inlineNS;
                if (schema != null && schema.ErrorCount == 0)
                {
                    try
                    {
                        SchemaInfo inlineSchemaInfo = new SchemaInfo();
                        inlineSchemaInfo.SchemaType = SchemaType.XSD;
                        inlineNS = schema.TargetNamespace ?? string.Empty;
                        if (!SchemaInfo!.TargetNamespaces.ContainsKey(inlineNS))
                        {
                            if (SchemaCollection!.Add(inlineNS, inlineSchemaInfo, schema, true) != null)
                            { //If no errors on compile
                                //Add to validator's SchemaInfo
                                SchemaInfo.Add(inlineSchemaInfo, EventHandler);
                            }
                        }
                    }
                    catch (XmlSchemaException e)
                    {
                        SendValidationEvent(SR.Sch_CannotLoadSchema, new string[] { BaseUri!.AbsoluteUri, e.Message }, XmlSeverityType.Error);
                    }
                }
                _inlineSchemaParser = null;
            }
        }
 
        private void ValidateElement()
        {
            elementName.Init(reader.LocalName, reader.NamespaceURI);
            object? particle = ValidateChildElement();
            if (IsXSDRoot(elementName.Name, elementName.Namespace) && reader.Depth > 0)
            {
                _inlineSchemaParser = new Parser(SchemaType.XSD, NameTable, SchemaNames, EventHandler);
                _inlineSchemaParser.StartParsing(reader, null);
                ProcessInlineSchema();
            }
            else
            {
                ProcessElement(particle);
            }
        }
 
        private object? ValidateChildElement()
        {
            object? particle = null;
            int errorCode;
            if (context!.NeedValidateChildren)
            {
                if (context.IsNill)
                {
                    SendValidationEvent(SR.Sch_ContentInNill, elementName.ToString());
                    return null;
                }
                particle = context.ElementDecl!.ContentValidator!.ValidateElement(elementName, context, out errorCode);
                if (particle == null)
                {
                    _processContents = context.ProcessContents = XmlSchemaContentProcessing.Skip;
                    if (errorCode == -2)
                    { //ContentModel all group error
                        SendValidationEvent(SR.Sch_AllElement, elementName.ToString());
                    }
                    XmlSchemaValidator.ElementValidationError(elementName, context, EventHandler, reader, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition, null);
                }
            }
            return particle;
        }
 
        private void ProcessElement(object? particle)
        {
            XmlQualifiedName xsiType;
            string? xsiNil;
            SchemaElementDecl? elementDecl = FastGetElementDecl(particle);
            Push(elementName);
            if (_bManageNamespaces)
            {
                _nsManager.PushScope();
            }
            ProcessXsiAttributes(out xsiType, out xsiNil);
            if (_processContents != XmlSchemaContentProcessing.Skip)
            {
                if (elementDecl == null || !xsiType.IsEmpty || xsiNil != null)
                {
                    elementDecl = ThoroughGetElementDecl(elementDecl, xsiType, xsiNil);
                }
                if (elementDecl == null)
                {
                    if (HasSchema && _processContents == XmlSchemaContentProcessing.Strict)
                    {
                        SendValidationEvent(SR.Sch_UndeclaredElement, XmlSchemaValidator.QNameString(context!.LocalName!, context.Namespace!));
                    }
                    else
                    {
                        SendValidationEvent(SR.Sch_NoElementSchemaFound, XmlSchemaValidator.QNameString(context!.LocalName!, context.Namespace!), XmlSeverityType.Warning);
                    }
                }
            }
 
            context!.ElementDecl = elementDecl;
            ValidateStartElementIdentityConstraints();
            ValidateStartElement();
            if (context.ElementDecl != null)
            {
                ValidateEndStartElement();
                context.NeedValidateChildren = _processContents != XmlSchemaContentProcessing.Skip;
                context.ElementDecl.ContentValidator!.InitValidation(context);
            }
        }
 
        // SxS: This method processes attributes read from source document and does not expose any resources.
        // It's OK to suppress the SxS warning.
        private void ProcessXsiAttributes(out XmlQualifiedName xsiType, out string? xsiNil)
        {
            string[]? xsiSchemaLocation = null;
            string? xsiNoNamespaceSchemaLocation = null;
            xsiType = XmlQualifiedName.Empty;
            xsiNil = null;
 
            if (reader.Depth == 0)
            {
                //Load schema for empty namespace
                LoadSchema(string.Empty, null);
 
                //Should load schemas for namespaces already added to nsManager
                foreach (string ns in _nsManager.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml).Values)
                {
                    LoadSchema(ns, null);
                }
            }
 
            if (reader.MoveToFirstAttribute())
            {
                do
                {
                    string objectNs = reader.NamespaceURI;
                    string objectName = reader.LocalName;
                    if (Ref.Equal(objectNs, _nsXmlNs))
                    {
                        LoadSchema(reader.Value, null);
                        if (_bManageNamespaces)
                        {
                            _nsManager.AddNamespace(reader.Prefix.Length == 0 ? string.Empty : reader.LocalName, reader.Value);
                        }
                    }
                    else if (Ref.Equal(objectNs, _nsXsi))
                    {
                        if (Ref.Equal(objectName, _xsiSchemaLocation))
                        {
                            xsiSchemaLocation = (string[])s_dtStringArray.ParseValue(reader.Value, NameTable, _nsManager);
                        }
                        else if (Ref.Equal(objectName, _xsiNoNamespaceSchemaLocation))
                        {
                            xsiNoNamespaceSchemaLocation = reader.Value;
                        }
                        else if (Ref.Equal(objectName, _xsiType))
                        {
                            xsiType = (XmlQualifiedName)s_dtQName.ParseValue(reader.Value, NameTable, _nsManager);
                        }
                        else if (Ref.Equal(objectName, _xsiNil))
                        {
                            xsiNil = reader.Value;
                        }
                    }
                } while (reader.MoveToNextAttribute());
                reader.MoveToElement();
            }
            if (xsiNoNamespaceSchemaLocation != null)
            {
                LoadSchema(string.Empty, xsiNoNamespaceSchemaLocation);
            }
            if (xsiSchemaLocation != null)
            {
                for (int i = 0; i < xsiSchemaLocation.Length - 1; i += 2)
                {
                    LoadSchema((string)xsiSchemaLocation[i], (string)xsiSchemaLocation[i + 1]);
                }
            }
        }
 
        private void ValidateEndElement()
        {
            if (_bManageNamespaces)
            {
                _nsManager.PopScope();
            }
            if (context!.ElementDecl != null)
            {
                if (!context.IsNill)
                {
                    if (context.NeedValidateChildren)
                    {
                        if (!context.ElementDecl.ContentValidator!.CompleteValidation(context))
                        {
                            XmlSchemaValidator.CompleteValidationError(context, EventHandler, reader, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition, null);
                        }
                    }
 
                    if (checkDatatype && !context.IsNill)
                    {
                        string stringValue = !hasSibling ? textString! : textValue!.ToString();  // only for identity-constraint exception reporting
                        if (!(stringValue.Length == 0 && context.ElementDecl.DefaultValueTyped != null))
                        {
                            CheckValue(stringValue, null);
                            checkDatatype = false;
                        }
                    }
                }
 
                // for each level in the stack, endchildren and fill value from element
                if (HasIdentityConstraints)
                {
                    EndElementIdentityConstraints();
                }
            }
            Pop();
        }
 
        private SchemaElementDecl? FastGetElementDecl(object? particle)
        {
            SchemaElementDecl? elementDecl = null;
            if (particle != null)
            {
                XmlSchemaElement? element = particle as XmlSchemaElement;
                if (element != null)
                {
                    elementDecl = element.ElementDecl;
                }
                else
                {
                    XmlSchemaAny any = (XmlSchemaAny)particle;
                    _processContents = any.ProcessContentsCorrect;
                }
            }
            return elementDecl;
        }
 
        private SchemaElementDecl? ThoroughGetElementDecl(SchemaElementDecl? elementDecl, XmlQualifiedName xsiType, string? xsiNil)
        {
            elementDecl ??= schemaInfo!.GetElementDecl(elementName);
            if (elementDecl != null)
            {
                if (xsiType.IsEmpty)
                {
                    if (elementDecl.IsAbstract)
                    {
                        SendValidationEvent(SR.Sch_AbstractElement, XmlSchemaValidator.QNameString(context!.LocalName!, context.Namespace!));
                        elementDecl = null;
                    }
                }
                else if (xsiNil != null && xsiNil.Equals("true"))
                {
                    SendValidationEvent(SR.Sch_XsiNilAndType);
                }
                else
                {
                    SchemaElementDecl? elementDeclXsi;
                    if (!schemaInfo!.ElementDeclsByType.TryGetValue(xsiType, out elementDeclXsi) && xsiType.Namespace == _nsXs)
                    {
                        XmlSchemaSimpleType? simpleType = DatatypeImplementation.GetSimpleTypeFromXsdType(new XmlQualifiedName(xsiType.Name, _nsXs));
                        if (simpleType != null)
                        {
                            elementDeclXsi = simpleType.ElementDecl;
                        }
                    }
                    if (elementDeclXsi == null)
                    {
                        SendValidationEvent(SR.Sch_XsiTypeNotFound, xsiType.ToString());
                        elementDecl = null;
                    }
                    else if (!XmlSchemaType.IsDerivedFrom(elementDeclXsi.SchemaType, elementDecl.SchemaType, elementDecl.Block))
                    {
                        SendValidationEvent(SR.Sch_XsiTypeBlockedEx, new string?[] { xsiType.ToString(), XmlSchemaValidator.QNameString(context!.LocalName!, context.Namespace!) });
                        elementDecl = null;
                    }
                    else
                    {
                        elementDecl = elementDeclXsi;
                    }
                }
                if (elementDecl != null && elementDecl.IsNillable)
                {
                    if (xsiNil != null)
                    {
                        context!.IsNill = XmlConvert.ToBoolean(xsiNil);
                        if (context.IsNill && elementDecl.DefaultValueTyped != null)
                        {
                            SendValidationEvent(SR.Sch_XsiNilAndFixed);
                        }
                    }
                }
                else if (xsiNil != null)
                {
                    SendValidationEvent(SR.Sch_InvalidXsiNill);
                }
            }
            return elementDecl;
        }
 
        private void ValidateStartElement()
        {
            if (context!.ElementDecl != null)
            {
                if (context.ElementDecl.IsAbstract)
                {
                    SendValidationEvent(SR.Sch_AbstractElement, XmlSchemaValidator.QNameString(context.LocalName!, context.Namespace!));
                }
 
                reader.SchemaTypeObject = context.ElementDecl.SchemaType;
 
                if (reader.IsEmptyElement && !context.IsNill && context.ElementDecl.DefaultValueTyped != null)
                {
                    reader.TypedValueObject = UnWrapUnion(context.ElementDecl.DefaultValueTyped);
                    context.IsNill = true; // reusing IsNill
                }
                else
                {
                    reader.TypedValueObject = null; //Typed value cleanup
                }
                if (this.context.ElementDecl.HasRequiredAttribute || HasIdentityConstraints)
                {
                    _attPresence.Clear();
                }
            }
 
            if (reader.MoveToFirstAttribute())
            {
                do
                {
                    if ((object)reader.NamespaceURI == (object)_nsXmlNs)
                    {
                        continue;
                    }
                    if ((object)reader.NamespaceURI == (object)_nsXsi)
                    {
                        continue;
                    }
 
                    try
                    {
                        reader.SchemaTypeObject = null;
                        XmlQualifiedName attQName = new XmlQualifiedName(reader.LocalName, reader.NamespaceURI);
                        bool skipContents = (_processContents == XmlSchemaContentProcessing.Skip);
                        SchemaAttDef? attnDef = schemaInfo!.GetAttributeXsd(context.ElementDecl, attQName, ref skipContents);
 
                        if (attnDef != null)
                        {
                            if (context.ElementDecl != null && (context.ElementDecl.HasRequiredAttribute || _startIDConstraint != -1))
                            {
                                _attPresence.Add(attnDef.Name, attnDef);
                            }
                            Debug.Assert(attnDef.SchemaType != null);
                            reader.SchemaTypeObject = attnDef.SchemaType;
                            if (attnDef.Datatype != null)
                            {
                                // need to check the contents of this attribute to make sure
                                // it is valid according to the specified attribute type.
                                CheckValue(reader.Value, attnDef);
                            }
                            if (HasIdentityConstraints)
                            {
                                AttributeIdentityConstraints(reader.LocalName, reader.NamespaceURI, reader.TypedValueObject, reader.Value, attnDef);
                            }
                        }
                        else if (!skipContents)
                        {
                            if (context.ElementDecl == null
                                && _processContents == XmlSchemaContentProcessing.Strict
                                && attQName.Namespace.Length != 0
                                && schemaInfo.Contains(attQName.Namespace)
                                )
                            {
                                SendValidationEvent(SR.Sch_UndeclaredAttribute, attQName.ToString());
                            }
                            else
                            {
                                SendValidationEvent(SR.Sch_NoAttributeSchemaFound, attQName.ToString(), XmlSeverityType.Warning);
                            }
                        }
                    }
                    catch (XmlSchemaException e)
                    {
                        e.SetSource(reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition);
                        SendValidationEvent(e);
                    }
                } while (reader.MoveToNextAttribute());
                reader.MoveToElement();
            }
        }
 
        private void ValidateEndStartElement()
        {
            if (context!.ElementDecl!.HasDefaultAttribute)
            {
                for (int i = 0; i < context.ElementDecl.DefaultAttDefs!.Count; ++i)
                {
                    SchemaAttDef attdef = (SchemaAttDef)context.ElementDecl.DefaultAttDefs[i];
                    reader.AddDefaultAttribute(attdef);
                    // even default attribute i have to move to... but can't exist
                    if (HasIdentityConstraints && !_attPresence.Contains(attdef.Name))
                    {
                        AttributeIdentityConstraints(attdef.Name.Name, attdef.Name.Namespace, UnWrapUnion(attdef.DefaultValueTyped), attdef.DefaultValueRaw, attdef);
                    }
                }
            }
 
            if (context.ElementDecl.HasRequiredAttribute)
            {
                try
                {
                    context.ElementDecl.CheckAttributes(_attPresence, reader.StandAlone);
                }
                catch (XmlSchemaException e)
                {
                    e.SetSource(reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition);
                    SendValidationEvent(e);
                }
            }
            if (context.ElementDecl.Datatype != null)
            {
                checkDatatype = true;
                hasSibling = false;
                textString = string.Empty;
                textValue!.Length = 0;
            }
        }
 
        private void LoadSchemaFromLocation(string uri, string url)
        {
            XmlTextReader? reader = null;
            SchemaInfo? schemaInfo = null;
 
            try
            {
                Uri ruri = this.XmlResolver!.ResolveUri(BaseUri, url);
                Stream stm = (Stream)this.XmlResolver.GetEntity(ruri, null, null)!;
                reader = new XmlTextReader(ruri.ToString(), stm, NameTable);
                //XmlSchema schema = SchemaCollection.Add(uri, reader, this.XmlResolver);
 
                Parser parser = new Parser(SchemaType.XSD, NameTable, SchemaNames, EventHandler);
                parser.XmlResolver = this.XmlResolver;
                SchemaType schemaType = parser.Parse(reader, uri);
 
                schemaInfo = new SchemaInfo();
                schemaInfo.SchemaType = schemaType;
                if (schemaType == SchemaType.XSD)
                {
                    if (SchemaCollection!.EventHandler == null)
                    {
                        SchemaCollection.EventHandler = this.EventHandler;
                    }
                    SchemaCollection.Add(uri, schemaInfo, parser.XmlSchema, true);
                }
                //Add to validator's SchemaInfo
                SchemaInfo!.Add(schemaInfo, EventHandler);
 
                while (reader.Read()) ; // wellformness check
            }
            catch (XmlSchemaException e)
            {
                schemaInfo = null;
                SendValidationEvent(SR.Sch_CannotLoadSchema, new string[] { uri, e.Message }, XmlSeverityType.Error);
            }
            catch (Exception e)
            {
                schemaInfo = null;
                SendValidationEvent(SR.Sch_CannotLoadSchema, new string[] { uri, e.Message }, XmlSeverityType.Warning);
            }
            finally
            {
                reader?.Close();
            }
        }
 
        private void LoadSchema(string uri, string? url)
        {
            if (this.XmlResolver == null)
            {
                return;
            }
            if (SchemaInfo!.TargetNamespaces.ContainsKey(uri) && _nsManager.LookupPrefix(uri) != null)
            {
                return;
            }
 
            SchemaInfo? schemaInfo = null;
            if (SchemaCollection != null)
                schemaInfo = SchemaCollection.GetSchemaInfo(uri);
            if (schemaInfo != null)
            {
                if (schemaInfo.SchemaType != SchemaType.XSD)
                {
                    throw new XmlException(SR.Xml_MultipleValidationTypes, string.Empty, this.PositionInfo.LineNumber, this.PositionInfo.LinePosition);
                }
                SchemaInfo.Add(schemaInfo, EventHandler);
                return;
            }
            if (url != null)
            {
                LoadSchemaFromLocation(uri, url);
            }
        }
 
        private bool HasSchema { get { return schemaInfo!.SchemaType != SchemaType.None; } }
 
        public override bool PreserveWhitespace
        {
            get { return context!.ElementDecl != null ? context.ElementDecl.ContentValidator!.PreserveWhitespace : false; }
        }
 
 
        private void ProcessTokenizedType(
            XmlTokenizedType ttype,
            string name
        )
        {
            switch (ttype)
            {
                case XmlTokenizedType.ID:
                    if (FindId(name) != null)
                    {
                        SendValidationEvent(SR.Sch_DupId, name);
                    }
                    else
                    {
                        AddID(name, context!.LocalName!);
                    }
                    break;
                case XmlTokenizedType.IDREF:
                    object? p = FindId(name);
                    if (p == null)
                    { // add it to linked list to check it later
                        _idRefListHead = new IdRefNode(_idRefListHead, name, this.PositionInfo.LineNumber, this.PositionInfo.LinePosition);
                    }
                    break;
                case XmlTokenizedType.ENTITY:
                    ProcessEntity(schemaInfo!, name, this, EventHandler, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition);
                    break;
                default:
                    break;
            }
        }
 
        private void CheckValue(
            string value,
            SchemaAttDef? attdef
        )
        {
            try
            {
                reader.TypedValueObject = null;
                bool isAttn = attdef != null;
                XmlSchemaDatatype? dtype = isAttn ? attdef!.Datatype : context!.ElementDecl!.Datatype;
                if (dtype == null)
                {
                    return; // no reason to check
                }
 
                object? typedValue = dtype.ParseValue(value, NameTable, _nsManager, true);
 
                // Check special types
                XmlTokenizedType ttype = dtype.TokenizedType;
                if (ttype == XmlTokenizedType.ENTITY || ttype == XmlTokenizedType.ID || ttype == XmlTokenizedType.IDREF)
                {
                    if (dtype.Variety == XmlSchemaDatatypeVariety.List)
                    {
                        string[] ss = (string[])typedValue;
                        for (int i = 0; i < ss.Length; ++i)
                        {
                            ProcessTokenizedType(dtype.TokenizedType, ss[i]);
                        }
                    }
                    else
                    {
                        ProcessTokenizedType(dtype.TokenizedType, (string)typedValue);
                    }
                }
 
                SchemaDeclBase decl = isAttn ? (SchemaDeclBase)attdef! : (SchemaDeclBase)context!.ElementDecl!;
                if (!decl.CheckValue(typedValue))
                {
                    if (isAttn)
                    {
                        SendValidationEvent(SR.Sch_FixedAttributeValue, attdef!.Name.ToString());
                    }
                    else
                    {
                        SendValidationEvent(SR.Sch_FixedElementValue, XmlSchemaValidator.QNameString(context!.LocalName!, context.Namespace!));
                    }
                }
                if (dtype.Variety == XmlSchemaDatatypeVariety.Union)
                {
                    typedValue = UnWrapUnion(typedValue);
                }
                reader.TypedValueObject = typedValue;
            }
            catch (XmlSchemaException)
            {
                if (attdef != null)
                {
                    SendValidationEvent(SR.Sch_AttributeValueDataType, attdef.Name.ToString());
                }
                else
                {
                    SendValidationEvent(SR.Sch_ElementValueDataType, XmlSchemaValidator.QNameString(context!.LocalName!, context.Namespace!));
                }
            }
        }
 
 
 
        internal void AddID(string name, object node)
        {
            // Note: It used to be true that we only called this if _fValidate was true,
            // but due to the fact that you can now dynamically type somethign as an ID
            // that is no longer true.
            _IDs ??= new Hashtable();
 
            _IDs.Add(name, node);
        }
 
        public override object? FindId(string name)
        {
            return _IDs?[name];
        }
 
        public bool IsXSDRoot(string localName, string ns)
        {
            return Ref.Equal(ns, _nsXs) && Ref.Equal(localName, _xsdSchema);
        }
 
        private void Push(XmlQualifiedName elementName)
        {
            context = (ValidationState)_validationStack.Push();
            if (context == null)
            {
                context = new ValidationState();
                _validationStack.AddToTop(context);
            }
 
            context.LocalName = elementName.Name;
            context.Namespace = elementName.Namespace;
            context.HasMatched = false;
            context.IsNill = false;
            context.ProcessContents = _processContents;
            context.NeedValidateChildren = false;
            context.Constr = null; //resetting the constraints to be null incase context != null
                                   // when pushing onto stack;
        }
 
 
        private void Pop()
        {
            if (_validationStack.Length > 1)
            {
                _validationStack.Pop();
                if (_startIDConstraint == _validationStack.Length)
                {
                    _startIDConstraint = -1;
                }
                context = (ValidationState?)_validationStack.Peek();
                _processContents = context!.ProcessContents;
            }
        }
 
        private void CheckForwardRefs()
        {
            IdRefNode? next = _idRefListHead;
            while (next != null)
            {
                if (FindId(next.Id) == null)
                {
                    SendValidationEvent(new XmlSchemaException(SR.Sch_UndeclaredId, next.Id, reader.BaseURI, next.LineNo, next.LinePos));
                }
                IdRefNode? ptr = next.Next;
                next.Next = null; // unhook each object so it is cleaned up by Garbage Collector
                next = ptr;
            }
            // not needed any more.
            _idRefListHead = null;
        }
 
 
        private void ValidateStartElementIdentityConstraints()
        {
            // added on June 15, set the context here, so the stack can have them
            if (context!.ElementDecl != null)
            {
                if (context.ElementDecl.Constraints != null)
                {
                    AddIdentityConstraints();
                }
                //foreach constraint in stack (including the current one)
                if (HasIdentityConstraints)
                {
                    ElementIdentityConstraints();
                }
            }
        }
 
        private bool HasIdentityConstraints
        {
            get { return _startIDConstraint != -1; }
        }
 
        private void AddIdentityConstraints()
        {
            context!.Constr = new ConstraintStruct[context.ElementDecl!.Constraints!.Length];
            int id = 0;
            for (int i = 0; i < context.ElementDecl.Constraints.Length; ++i)
            {
                context.Constr[id++] = new ConstraintStruct(context.ElementDecl.Constraints[i]);
            } // foreach constraint /constraintstruct
 
            // added on June 19, make connections between new keyref tables with key/unique tables in stack
            // i can't put it in the above loop, because there will be key on the same level
            for (int i = 0; i < context.Constr.Length; ++i)
            {
                if (context.Constr[i].constraint.Role == CompiledIdentityConstraint.ConstraintRole.Keyref)
                {
                    bool find = false;
                    // go upwards checking or only in this level
                    for (int level = _validationStack.Length - 1; level >= ((_startIDConstraint >= 0) ? _startIDConstraint : _validationStack.Length - 1); level--)
                    {
                        // no constraint for this level
                        if (((ValidationState)(_validationStack[level])).Constr == null)
                        {
                            continue;
                        }
                        // else
                        ConstraintStruct[] constraints = ((ValidationState)_validationStack[level]).Constr!;
                        for (int j = 0; j < constraints.Length; ++j)
                        {
                            if (constraints[j].constraint.name == context.Constr[i].constraint.refer)
                            {
                                find = true;
                                if (constraints[j].keyrefTable == null)
                                {
                                    constraints[j].keyrefTable = new Hashtable();
                                }
                                context.Constr[i].qualifiedTable = constraints[j].keyrefTable;
                                break;
                            }
                        }
 
                        if (find)
                        {
                            break;
                        }
                    }
                    if (!find)
                    {
                        // didn't find connections, throw exceptions
                        SendValidationEvent(SR.Sch_RefNotInScope, XmlSchemaValidator.QNameString(context.LocalName!, context.Namespace!));
                    }
                } // finished dealing with keyref
            }  // end foreach
 
            // initial set
            if (_startIDConstraint == -1)
            {
                _startIDConstraint = _validationStack.Length - 1;
            }
        }
 
        private void ElementIdentityConstraints()
        {
            for (int i = _startIDConstraint; i < _validationStack.Length; i++)
            {
                // no constraint for this level
                if (((ValidationState)(_validationStack[i])).Constr == null)
                {
                    continue;
                }
 
                // else
                ConstraintStruct[] constraints = ((ValidationState)_validationStack[i]).Constr!;
                for (int j = 0; j < constraints.Length; ++j)
                {
                    // check selector from here
                    if (constraints[j].axisSelector.MoveToStartElement(reader.LocalName, reader.NamespaceURI))
                    {
                        // selector selects new node, activate a new set of fields
                        Debug.WriteLine("Selector Match!");
                        Debug.WriteLine($"Name: {reader.LocalName}\t|\tURI: {reader.NamespaceURI}\n");
                        // in which axisFields got updated
                        constraints[j].axisSelector.PushKS(PositionInfo.LineNumber, PositionInfo.LinePosition);
                    }
 
                    // axisFields is not null, but may be empty
                    for (int k = 0; k < constraints[j].axisFields.Count; ++k)
                    {
                        LocatedActiveAxis laxis = (LocatedActiveAxis)constraints[j].axisFields[k]!;
 
                        // check field from here
                        if (laxis.MoveToStartElement(reader.LocalName, reader.NamespaceURI))
                        {
                            Debug.WriteLine("Element Field Match!");
                            // checking simpleType / simpleContent
                            if (context!.ElementDecl != null)
                            {      // nextElement can be null when xml/xsd are not valid
                                if (context.ElementDecl.Datatype == null)
                                {
                                    SendValidationEvent(SR.Sch_FieldSimpleTypeExpected, reader.LocalName);
                                }
                                else
                                {
                                    // can't fill value here, wait till later....
                                    // fill type : xsdType
                                    laxis.isMatched = true;
                                    // since it's simpletyped element, the endchildren will come consequently... don't worry
                                }
                            }
                        }
                    }
                }
            }
        }
 
        // facilitate modifying
        private void AttributeIdentityConstraints(string name, string ns, object? obj, string sobj, SchemaAttDef attdef)
        {
            for (int ci = _startIDConstraint; ci < _validationStack.Length; ci++)
            {
                // no constraint for this level
                if (((ValidationState)(_validationStack[ci])).Constr == null)
                {
                    continue;
                }
 
                // else
                ConstraintStruct[] constraints = ((ValidationState)_validationStack[ci]).Constr!;
                for (int i = 0; i < constraints.Length; ++i)
                {
                    // axisFields is not null, but may be empty
                    for (int j = 0; j < constraints[i].axisFields.Count; ++j)
                    {
                        LocatedActiveAxis laxis = (LocatedActiveAxis)constraints[i].axisFields[j]!;
 
                        // check field from here
                        if (laxis.MoveToAttribute(name, ns))
                        {
                            Debug.WriteLine("Attribute Field Match!");
                            //attribute is only simpletype, so needn't checking...
                            // can fill value here, yeah!!
                            Debug.WriteLine("Attribute Field Filling Value!");
                            Debug.WriteLine($"Name: {name}\t|\tURI: {ns}\t|\tValue: {obj}\n");
                            if (laxis.Ks[laxis.Column] != null)
                            {
                                // should be evaluated to either an empty node-set or a node-set with exactly one member
                                // two matches...
                                SendValidationEvent(SR.Sch_FieldSingleValueExpected, name);
                            }
                            else if ((attdef != null) && (attdef.Datatype != null))
                            {
                                laxis.Ks[laxis.Column] = new TypedObject(obj, sobj, attdef.Datatype);
                            }
                        }
                    }
                }
            }
        }
 
        private static object? UnWrapUnion(object? typedValue)
        {
            XsdSimpleValue? simpleValue = typedValue as XsdSimpleValue;
            if (simpleValue != null)
            {
                typedValue = simpleValue.TypedValue;
            }
 
            return typedValue;
        }
 
        private void EndElementIdentityConstraints()
        {
            for (int ci = _validationStack.Length - 1; ci >= _startIDConstraint; ci--)
            {
                // no constraint for this level
                if (((ValidationState)(_validationStack[ci])).Constr == null)
                {
                    continue;
                }
 
                // else
                ConstraintStruct[] constraints = ((ValidationState)_validationStack[ci]).Constr!;
                for (int i = 0; i < constraints.Length; ++i)
                {
                    // EndChildren
                    // axisFields is not null, but may be empty
                    for (int j = 0; j < constraints[i].axisFields.Count; ++j)
                    {
                        LocatedActiveAxis laxis = (LocatedActiveAxis)constraints[i].axisFields[j]!;
                        // check field from here
                        // isMatched is false when nextElement is null. so needn't change this part.
                        if (laxis.isMatched)
                        {
                            Debug.WriteLine("Element Field Filling Value!");
                            Debug.WriteLine($"Name: {reader.LocalName}\t|\tURI: {reader.NamespaceURI}\t|\tValue: {reader.TypedValueObject}\n");
                            // fill value
                            laxis.isMatched = false;
                            if (laxis.Ks[laxis.Column] != null)
                            {
                                // [field...] should be evaluated to either an empty node-set or a node-set with exactly one member
                                // two matches... already existing field value in the table.
                                SendValidationEvent(SR.Sch_FieldSingleValueExpected, reader.LocalName);
                            }
                            else
                            {
                                // for element, reader.Value = "";
                                string stringValue = !hasSibling ? textString! : textValue!.ToString();  // only for identity-constraint exception reporting
                                if (reader.TypedValueObject != null && stringValue.Length != 0)
                                {
                                    laxis.Ks[laxis.Column] = new TypedObject(reader.TypedValueObject, stringValue, context!.ElementDecl!.Datatype);
                                }
                            }
                        }
                        // EndChildren
                        laxis.EndElement(reader.LocalName, reader.NamespaceURI);
                    }
 
                    if (constraints[i].axisSelector.EndElement(reader.LocalName, reader.NamespaceURI))
                    {
                        // insert key sequence into hash (+ located active axis tuple leave for later)
                        KeySequence ks = constraints[i].axisSelector.PopKS();
                        // unqualified keysequence are not allowed
                        switch (constraints[i].constraint.Role)
                        {
                            case CompiledIdentityConstraint.ConstraintRole.Key:
                                if (!ks.IsQualified())
                                {
                                    //Key's fields can't be null...  if we can return context node's line info maybe it will be better
                                    //only keymissing & keyduplicate reporting cases are necessary to be dealt with... 3 places...
                                    SendValidationEvent(new XmlSchemaException(SR.Sch_MissingKey, constraints[i].constraint.name.ToString(), reader.BaseURI, ks.PosLine, ks.PosCol));
                                }
                                else if (constraints[i].qualifiedTable!.Contains(ks))
                                {
                                    // unique or key checking value confliction
                                    // for redundant key, reporting both occurrings
                                    // doesn't work... how can i retrieve value out??
                                    SendValidationEvent(new XmlSchemaException(SR.Sch_DuplicateKey,
                                        new string[2] { ks.ToString(), constraints[i].constraint.name.ToString() },
                                        reader.BaseURI, ks.PosLine, ks.PosCol));
                                }
                                else
                                {
                                    constraints[i].qualifiedTable!.Add(ks, ks);
                                }
                                break;
                            case CompiledIdentityConstraint.ConstraintRole.Unique:
                                if (!ks.IsQualified())
                                {
                                    continue;
                                }
                                if (constraints[i].qualifiedTable!.Contains(ks))
                                {
                                    // unique or key checking confliction
                                    SendValidationEvent(new XmlSchemaException(SR.Sch_DuplicateKey,
                                        new string[2] { ks.ToString(), constraints[i].constraint.name.ToString() },
                                        reader.BaseURI, ks.PosLine, ks.PosCol));
                                }
                                else
                                {
                                    constraints[i].qualifiedTable!.Add(ks, ks);
                                }
                                break;
                            case CompiledIdentityConstraint.ConstraintRole.Keyref:
                                // is there any possibility:
                                // 2 keyrefs: value is equal, type is not
                                // both put in the hashtable, 1 reference, 1 not
                                if (constraints[i].qualifiedTable != null)
                                { //Will be null in cases when the keyref is outside the scope of the key, that is not allowed by our impl
                                    if (!ks.IsQualified() || constraints[i].qualifiedTable!.Contains(ks))
                                    {
                                        continue;
                                    }
                                    constraints[i].qualifiedTable!.Add(ks, ks);
                                }
                                break;
                        }
                    }
                }
            }
 
            // current level's constraint struct
            ConstraintStruct[] vcs = ((ValidationState)(_validationStack[_validationStack.Length - 1])).Constr!;
            if (vcs != null)
            {
                // validating all referencing tables...
                for (int i = 0; i < vcs.Length; ++i)
                {
                    if ((vcs[i].constraint.Role == CompiledIdentityConstraint.ConstraintRole.Keyref)
                        || (vcs[i].keyrefTable == null))
                    {
                        continue;
                    }
                    foreach (KeySequence? ks in vcs[i].keyrefTable!.Keys)
                    {
                        if (!vcs[i].qualifiedTable!.Contains(ks!))
                        {
                            SendValidationEvent(new XmlSchemaException(SR.Sch_UnresolvedKeyref, new string[2] { ks!.ToString(), vcs[i].constraint.name.ToString() },
                                reader.BaseURI, ks.PosLine, ks.PosCol));
                        }
                    }
                }
            }
        }
    }
#pragma warning restore 618
}