File: System\Xml\Schema\DtdValidator.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;
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
using System.Text;
using System.Xml.Schema;
using System.Xml.XPath;
 
namespace System.Xml.Schema
{
#pragma warning disable 618
 
    internal sealed class DtdValidator : BaseValidator
    {
        //required by ParseValue
        private sealed class NamespaceManager : XmlNamespaceManager
        {
            public override string LookupNamespace(string prefix) { return prefix; }
        }
 
        private static readonly NamespaceManager s_namespaceManager = new NamespaceManager();
        private const int STACK_INCREMENT = 10;
        private HWStack _validationStack;  // validaton contexts
        private Hashtable _attPresence;
        private XmlQualifiedName _name = XmlQualifiedName.Empty;
        private Hashtable? _IDs;
        private IdRefNode? _idRefListHead;
        private readonly bool _processIdentityConstraints;
 
        internal DtdValidator(XmlValidatingReaderImpl reader, IValidationEventHandling eventHandling, bool processIdentityConstraints) : base(reader, null, eventHandling)
        {
            _processIdentityConstraints = processIdentityConstraints;
            Init();
        }
 
        [MemberNotNull(nameof(_validationStack))]
        [MemberNotNull(nameof(_name))]
        [MemberNotNull(nameof(_attPresence))]
        private void Init()
        {
            Debug.Assert(reader != null);
            _validationStack = new HWStack(STACK_INCREMENT);
            textValue = new StringBuilder();
            _name = XmlQualifiedName.Empty;
            _attPresence = new Hashtable();
            schemaInfo = new SchemaInfo();
            checkDatatype = false;
            Push(_name);
        }
 
        public override void Validate()
        {
            if (schemaInfo!.SchemaType == SchemaType.DTD)
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        ValidateElement();
                        if (reader.IsEmptyElement)
                        {
                            goto case XmlNodeType.EndElement;
                        }
                        break;
                    case XmlNodeType.Whitespace:
                    case XmlNodeType.SignificantWhitespace:
                        if (MeetsStandAloneConstraint())
                        {
                            ValidateWhitespace();
                        }
                        break;
                    case XmlNodeType.ProcessingInstruction:
                    case XmlNodeType.Comment:
                        ValidatePIComment();
                        break;
 
                    case XmlNodeType.Text:          // text inside a node
                    case XmlNodeType.CDATA:         // <![CDATA[...]]>
                        ValidateText();
                        break;
                    case XmlNodeType.EntityReference:
                        if (!GenEntity(new XmlQualifiedName(reader.LocalName, reader.Prefix)))
                        {
                            ValidateText();
                        }
                        break;
                    case XmlNodeType.EndElement:
                        ValidateEndElement();
                        break;
                }
            }
            else
            {
                if (reader.Depth == 0 &&
                    reader.NodeType == XmlNodeType.Element)
                {
                    SendValidationEvent(SR.Xml_NoDTDPresent, _name.ToString(), XmlSeverityType.Warning);
                }
            }
        }
 
        private bool MeetsStandAloneConstraint()
        {
            if (reader.StandAlone &&                  // VC 1 - iv
                 context!.ElementDecl != null &&
                 context.ElementDecl.IsDeclaredInExternal &&
                 context.ElementDecl.ContentValidator!.ContentType == XmlSchemaContentType.ElementOnly)
            {
                SendValidationEvent(SR.Sch_StandAlone);
                return false;
            }
            return true;
        }
 
        private void ValidatePIComment()
        {
            // When validating with a dtd, empty elements should be lexically empty.
            if (context!.NeedValidateChildren)
            {
                if (context!.ElementDecl!.ContentValidator == ContentValidator.Empty)
                {
                    SendValidationEvent(SR.Sch_InvalidPIComment);
                }
            }
        }
 
        private void ValidateElement()
        {
            elementName.Init(reader.LocalName, reader.Prefix);
            if ((reader.Depth == 0) &&
                  (!schemaInfo!.DocTypeName.IsEmpty) &&
                  (!schemaInfo.DocTypeName.Equals(elementName)))
            { //VC 1
                SendValidationEvent(SR.Sch_RootMatchDocType);
            }
            else
            {
                ValidateChildElement();
            }
            ProcessElement();
        }
 
        private void ValidateChildElement()
        {
            Debug.Assert(reader.NodeType == XmlNodeType.Element);
            if (context!.NeedValidateChildren)
            { //i think i can get away with removing this if cond since won't make this call for documentelement
                int errorCode;
                context.ElementDecl!.ContentValidator!.ValidateElement(elementName, context, out errorCode);
                if (errorCode < 0)
                {
                    XmlSchemaValidator.ElementValidationError(elementName, context, EventHandler, reader, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition, null);
                }
            }
        }
 
        private void ValidateStartElement()
        {
            if (context!.ElementDecl != null)
            {
                Reader.SchemaTypeObject = context.ElementDecl.SchemaType;
 
                if (Reader.IsEmptyElement && context.ElementDecl.DefaultValueTyped != null)
                {
                    Reader.TypedValueObject = context.ElementDecl.DefaultValueTyped;
                    context.IsNill = true; // reusing IsNill - what is this flag later used for??
                }
                if (context.ElementDecl.HasRequiredAttribute)
                {
                    _attPresence.Clear();
                }
            }
 
            if (Reader.MoveToFirstAttribute())
            {
                do
                {
                    try
                    {
                        reader.SchemaTypeObject = null;
                        SchemaAttDef? attnDef = context.ElementDecl!.GetAttDef(new XmlQualifiedName(reader.LocalName, reader.Prefix));
                        if (attnDef != null)
                        {
                            if (context.ElementDecl != null && context.ElementDecl.HasRequiredAttribute)
                            {
                                _attPresence.Add(attnDef.Name, attnDef);
                            }
                            Reader.SchemaTypeObject = attnDef.SchemaType;
 
                            if (attnDef.Datatype != null && !reader.IsDefault)
                            { //Since XmlTextReader adds default attributes, do not check again
                                // set typed value
                                CheckValue(Reader.Value, attnDef);
                            }
                        }
                        else
                        {
                            SendValidationEvent(SR.Sch_UndeclaredAttribute, reader.Name);
                        }
                    }
                    catch (XmlSchemaException e)
                    {
                        e.SetSource(Reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition);
                        SendValidationEvent(e);
                    }
                } while (Reader.MoveToNextAttribute());
                Reader.MoveToElement();
            }
        }
 
        private void ValidateEndStartElement()
        {
            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 ProcessElement()
        {
            SchemaElementDecl? elementDecl = schemaInfo!.GetElementDecl(elementName);
            Push(elementName);
            if (elementDecl != null)
            {
                context!.ElementDecl = elementDecl;
                ValidateStartElement();
                ValidateEndStartElement();
                context.NeedValidateChildren = true;
                elementDecl.ContentValidator!.InitValidation(context);
            }
            else
            {
                SendValidationEvent(SR.Sch_UndeclaredElement, XmlSchemaValidator.QNameString(context!.LocalName!, context.Namespace!));
                context.ElementDecl = null;
            }
        }
 
        public override void CompleteValidation()
        {
            if (schemaInfo!.SchemaType == SchemaType.DTD)
            {
                do
                {
                    ValidateEndElement();
                } while (Pop());
                CheckForwardRefs();
            }
        }
 
        private void ValidateEndElement()
        {
            if (context!.ElementDecl != null)
            {
                if (context.NeedValidateChildren)
                {
                    if (!context.ElementDecl.ContentValidator!.CompleteValidation(context))
                    {
                        XmlSchemaValidator.CompleteValidationError(context, EventHandler, reader, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition, null);
                    }
                }
 
                if (checkDatatype)
                {
                    string stringValue = !hasSibling ? textString! : textValue!.ToString()!;  // only for identity-constraint exception reporting
                    CheckValue(stringValue, null);
                    checkDatatype = false;
                    textValue!.Length = 0; // cleanup
                    textString = string.Empty;
                }
            }
            Pop();
        }
 
        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 (_processIdentityConstraints)
                    {
                        if (FindId(name) != null)
                        {
                            SendValidationEvent(SR.Sch_DupId, name);
                        }
                        else
                        {
                            AddID(name, context!.LocalName!);
                        }
                    }
                    break;
                case XmlTokenizedType.IDREF:
                    if (_processIdentityConstraints)
                    {
                        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;
            }
        }
 
        //check the contents of this attribute to ensure it is valid according to the specified attribute type.
        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
                }
 
                if (dtype.TokenizedType != XmlTokenizedType.CDATA)
                {
                    value = value.Trim();
                }
 
                object typedValue = dtype.ParseValue(value, NameTable, s_namespaceManager);
                reader.TypedValueObject = typedValue;
                // 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.Values != null && !decl.CheckEnumeration(typedValue))
                {
                    if (dtype.TokenizedType == XmlTokenizedType.NOTATION)
                    {
                        SendValidationEvent(SR.Sch_NotationValue, typedValue.ToString());
                    }
                    else
                    {
                        SendValidationEvent(SR.Sch_EnumerationValue, typedValue.ToString());
                    }
                }
                if (!decl.CheckValue(typedValue))
                {
                    if (isAttn)
                    {
                        SendValidationEvent(SR.Sch_FixedAttributeValue, attdef!.Name.ToString());
                    }
                    else
                    {
                        SendValidationEvent(SR.Sch_FixedElementValue, XmlSchemaValidator.QNameString(context!.LocalName!, context.Namespace!));
                    }
                }
            }
            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];
        }
 
        private bool GenEntity(XmlQualifiedName qname)
        {
            string n = qname.Name;
            if (n[0] == '#')
            { // char entity reference
                return false;
            }
            else if (SchemaEntity.IsPredefinedEntity(n))
            {
                return false;
            }
            else
            {
                SchemaEntity? en = GetEntity(qname, false);
                if (en == null)
                {
                    // well-formness error, see xml spec [68]
                    throw new XmlException(SR.Xml_UndeclaredEntity, n);
                }
                if (!en.NData.IsEmpty)
                {
                    // well-formness error, see xml spec [68]
                    throw new XmlException(SR.Xml_UnparsedEntityRef, n);
                }
 
                if (reader.StandAlone && en.DeclaredInExternal)
                {
                    SendValidationEvent(SR.Sch_StandAlone);
                }
                return true;
            }
        }
 
 
        private SchemaEntity? GetEntity(XmlQualifiedName qname, bool fParameterEntity)
        {
            SchemaEntity? entity;
            if (fParameterEntity)
            {
                if (schemaInfo!.ParameterEntities.TryGetValue(qname, out entity))
                {
                    return entity;
                }
            }
            else
            {
                if (schemaInfo!.GeneralEntities.TryGetValue(qname, out entity))
                {
                    return entity;
                }
            }
            return null;
        }
 
        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 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.NeedValidateChildren = false;
        }
 
        private bool Pop()
        {
            if (_validationStack.Length > 1)
            {
                _validationStack.Pop();
                context = (ValidationState?)_validationStack.Peek();
                return true;
            }
            return false;
        }
 
        public static void SetDefaultTypedValue(
            SchemaAttDef attdef,
            IDtdParserAdapter readerAdapter
        )
        {
            try
            {
                string value = attdef.DefaultValueExpanded;
                XmlSchemaDatatype dtype = attdef.Datatype;
                if (dtype == null)
                {
                    return; // no reason to check
                }
                if (dtype.TokenizedType != XmlTokenizedType.CDATA)
                {
                    value = value.Trim();
                }
                attdef.DefaultValueTyped = dtype.ParseValue(value, readerAdapter.NameTable, readerAdapter.NamespaceResolver);
            }
#if DEBUG
            catch (XmlSchemaException)
#else
            catch (Exception)
#endif
            {
                IValidationEventHandling? eventHandling = ((IDtdParserAdapterWithValidation)readerAdapter).ValidationEventHandling;
                if (eventHandling != null)
                {
                    XmlSchemaException e = new XmlSchemaException(SR.Sch_AttributeDefaultDataType, attdef.Name.ToString());
                    eventHandling.SendEvent(e, XmlSeverityType.Error);
                }
            }
        }
 
        public static void CheckDefaultValue(
            SchemaAttDef attdef,
            SchemaInfo sinfo,
            IValidationEventHandling eventHandling,
            string? baseUriStr
        )
        {
            try
            {
                baseUriStr ??= string.Empty;
                XmlSchemaDatatype dtype = attdef.Datatype;
                if (dtype == null)
                {
                    return; // no reason to check
                }
                object? typedValue = attdef.DefaultValueTyped;
 
                // Check special types
                XmlTokenizedType ttype = dtype.TokenizedType;
                if (ttype == XmlTokenizedType.ENTITY)
                {
                    if (dtype.Variety == XmlSchemaDatatypeVariety.List)
                    {
                        string[] ss = (string[])typedValue!;
                        for (int i = 0; i < ss.Length; ++i)
                        {
                            ProcessEntity(sinfo, ss[i], eventHandling, baseUriStr, attdef.ValueLineNumber, attdef.ValueLinePosition);
                        }
                    }
                    else
                    {
                        ProcessEntity(sinfo, (string)typedValue!, eventHandling, baseUriStr, attdef.ValueLineNumber, attdef.ValueLinePosition);
                    }
                }
                else if (ttype == XmlTokenizedType.ENUMERATION)
                {
                    if (!attdef.CheckEnumeration(typedValue!))
                    {
                        if (eventHandling != null)
                        {
                            XmlSchemaException e = new XmlSchemaException(SR.Sch_EnumerationValue, typedValue!.ToString(), baseUriStr, attdef.ValueLineNumber, attdef.ValueLinePosition);
                            eventHandling.SendEvent(e, XmlSeverityType.Error);
                        }
                    }
                }
            }
#if DEBUG
            catch (XmlSchemaException)
#else
            catch (Exception)
#endif
            {
 
                if (eventHandling != null)
                {
                    XmlSchemaException e = new XmlSchemaException(SR.Sch_AttributeDefaultDataType, attdef.Name.ToString());
                    eventHandling.SendEvent(e, XmlSeverityType.Error);
                }
            }
        }
    }
#pragma warning restore 618
}