1 //------------------------------------------------------------------------------
2 // <copyright file="DtdValidator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 namespace System.Xml.Schema {
10 using System.Collections;
14 using System.Diagnostics;
15 using System.Xml.Schema;
16 using System.Xml.XPath;
18 #pragma warning disable 618
20 internal sealed class DtdValidator : BaseValidator {
22 //required by ParseValue
23 class NamespaceManager : XmlNamespaceManager {
24 public override string LookupNamespace(string prefix) { return prefix; }
27 static NamespaceManager namespaceManager = new NamespaceManager();
28 const int STACK_INCREMENT = 10;
29 HWStack validationStack; // validaton contexts
30 Hashtable attPresence;
31 XmlQualifiedName name = XmlQualifiedName.Empty;
33 IdRefNode idRefListHead;
34 bool processIdentityConstraints;
36 internal DtdValidator(XmlValidatingReaderImpl reader, IValidationEventHandling eventHandling, bool processIdentityConstraints) : base(reader, null, eventHandling) {
37 this.processIdentityConstraints = processIdentityConstraints;
42 Debug.Assert(reader != null);
43 validationStack = new HWStack(STACK_INCREMENT);
44 textValue = new StringBuilder();
45 name = XmlQualifiedName.Empty;
46 attPresence = new Hashtable();
47 schemaInfo = new SchemaInfo();
48 checkDatatype = false;
52 public override void Validate() {
53 if (schemaInfo.SchemaType == SchemaType.DTD) {
54 switch (reader.NodeType) {
55 case XmlNodeType.Element:
57 if (reader.IsEmptyElement) {
58 goto case XmlNodeType.EndElement;
61 case XmlNodeType.Whitespace:
62 case XmlNodeType.SignificantWhitespace:
63 if (MeetsStandAloneConstraint()) {
67 case XmlNodeType.ProcessingInstruction:
68 case XmlNodeType.Comment:
72 case XmlNodeType.Text: // text inside a node
73 case XmlNodeType.CDATA: // <![CDATA[...]]>
76 case XmlNodeType.EntityReference:
77 if (!GenEntity( new XmlQualifiedName(reader.LocalName, reader.Prefix) ) ){
81 case XmlNodeType.EndElement:
87 if(reader.Depth == 0 &&
88 reader.NodeType == XmlNodeType.Element) {
89 SendValidationEvent(Res.Xml_NoDTDPresent, this.name.ToString(), XmlSeverityType.Warning);
94 private bool MeetsStandAloneConstraint() {
95 if (reader.StandAlone && // VC 1 - iv
96 context.ElementDecl != null &&
97 context.ElementDecl.IsDeclaredInExternal &&
98 context.ElementDecl.ContentValidator.ContentType == XmlSchemaContentType.ElementOnly) {
99 SendValidationEvent(Res.Sch_StandAlone);
105 private void ValidatePIComment() {
106 // When validating with a dtd, empty elements should be lexically empty.
107 if (context.NeedValidateChildren ) {
108 if (context.ElementDecl.ContentValidator == ContentValidator.Empty) {
109 SendValidationEvent(Res.Sch_InvalidPIComment);
115 private void ValidateElement() {
116 elementName.Init(reader.LocalName, reader.Prefix);
117 if ( (reader.Depth == 0) &&
118 (!schemaInfo.DocTypeName.IsEmpty) &&
119 (!schemaInfo.DocTypeName.Equals(elementName)) ){ //VC 1
120 SendValidationEvent(Res.Sch_RootMatchDocType);
123 ValidateChildElement();
128 private void ValidateChildElement() {
129 Debug.Assert(reader.NodeType == XmlNodeType.Element);
130 if (context.NeedValidateChildren) { //i think i can get away with removing this if cond since won't make this call for documentelement
132 context.ElementDecl.ContentValidator.ValidateElement(elementName, context, out errorCode);
134 XmlSchemaValidator.ElementValidationError(elementName, context, EventHandler, reader, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition, null);
139 private void ValidateStartElement() {
140 if (context.ElementDecl != null) {
141 Reader.SchemaTypeObject = context.ElementDecl.SchemaType;
143 if (Reader.IsEmptyElement && context.ElementDecl.DefaultValueTyped != null) {
144 Reader.TypedValueObject = context.ElementDecl.DefaultValueTyped;
145 context.IsNill = true; // reusing IsNill - what is this flag later used for??
147 if ( context.ElementDecl.HasRequiredAttribute ) {
152 if (Reader.MoveToFirstAttribute()) {
155 reader.SchemaTypeObject = null;
156 SchemaAttDef attnDef = context.ElementDecl.GetAttDef( new XmlQualifiedName( reader.LocalName, reader.Prefix) );
157 if (attnDef != null) {
158 if (context.ElementDecl != null && context.ElementDecl.HasRequiredAttribute) {
159 attPresence.Add(attnDef.Name, attnDef);
161 Reader.SchemaTypeObject = attnDef.SchemaType;
163 if (attnDef.Datatype != null && !reader.IsDefault) { //Since XmlTextReader adds default attributes, do not check again
165 CheckValue(Reader.Value, attnDef);
169 SendValidationEvent(Res.Sch_UndeclaredAttribute, reader.Name);
172 catch (XmlSchemaException e) {
173 e.SetSource(Reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition);
174 SendValidationEvent(e);
176 } while(Reader.MoveToNextAttribute());
177 Reader.MoveToElement();
182 private void ValidateEndStartElement() {
183 if (context.ElementDecl.HasRequiredAttribute) {
185 context.ElementDecl.CheckAttributes(attPresence, Reader.StandAlone);
187 catch (XmlSchemaException e) {
188 e.SetSource(Reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition);
189 SendValidationEvent(e);
193 if (context.ElementDecl.Datatype != null) {
194 checkDatatype = true;
196 textString = string.Empty;
197 textValue.Length = 0;
201 private void ProcessElement() {
202 SchemaElementDecl elementDecl = schemaInfo.GetElementDecl(elementName);
204 if (elementDecl != null) {
205 context.ElementDecl = elementDecl;
206 ValidateStartElement();
207 ValidateEndStartElement();
208 context.NeedValidateChildren = true;
209 elementDecl.ContentValidator.InitValidation( context );
212 SendValidationEvent(Res.Sch_UndeclaredElement, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace));
213 context.ElementDecl = null;
217 public override void CompleteValidation() {
218 if (schemaInfo.SchemaType == SchemaType.DTD) {
220 ValidateEndElement();
226 private void ValidateEndElement() {
227 if (context.ElementDecl != null) {
228 if (context.NeedValidateChildren) {
229 if(!context.ElementDecl.ContentValidator.CompleteValidation(context)) {
230 XmlSchemaValidator.CompleteValidationError(context, EventHandler, reader, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition, null);
235 string stringValue = !hasSibling ? textString : textValue.ToString(); // only for identity-constraint exception reporting
236 CheckValue(stringValue, null);
237 checkDatatype = false;
238 textValue.Length = 0; // cleanup
239 textString = string.Empty;
246 public override bool PreserveWhitespace {
247 get { return context.ElementDecl != null ? context.ElementDecl.ContentValidator.PreserveWhitespace : false; }
251 void ProcessTokenizedType(
252 XmlTokenizedType ttype,
256 case XmlTokenizedType.ID:
257 if (processIdentityConstraints) {
258 if (FindId(name) != null) {
259 SendValidationEvent(Res.Sch_DupId, name);
262 AddID(name, context.LocalName);
266 case XmlTokenizedType.IDREF:
267 if (processIdentityConstraints) {
268 object p = FindId(name);
269 if (p == null) { // add it to linked list to check it later
270 idRefListHead = new IdRefNode(idRefListHead, name, this.PositionInfo.LineNumber, this.PositionInfo.LinePosition);
275 case XmlTokenizedType.ENTITY:
276 ProcessEntity(schemaInfo, name, this, EventHandler, Reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition);
283 //check the contents of this attribute to ensure it is valid according to the specified attribute type.
284 private void CheckValue(string value, SchemaAttDef attdef) {
286 reader.TypedValueObject = null;
287 bool isAttn = attdef != null;
288 XmlSchemaDatatype dtype = isAttn ? attdef.Datatype : context.ElementDecl.Datatype;
290 return; // no reason to check
293 if (dtype.TokenizedType != XmlTokenizedType.CDATA) {
294 value = value.Trim();
297 object typedValue = dtype.ParseValue(value, NameTable, namespaceManager);
298 reader.TypedValueObject = typedValue;
299 // Check special types
300 XmlTokenizedType ttype = dtype.TokenizedType;
301 if (ttype == XmlTokenizedType.ENTITY || ttype == XmlTokenizedType.ID || ttype == XmlTokenizedType.IDREF) {
302 if (dtype.Variety == XmlSchemaDatatypeVariety.List) {
303 string[] ss = (string[])typedValue;
304 for (int i = 0; i < ss.Length; ++i) {
305 ProcessTokenizedType(dtype.TokenizedType, ss[i]);
309 ProcessTokenizedType(dtype.TokenizedType, (string)typedValue);
313 SchemaDeclBase decl = isAttn ? (SchemaDeclBase)attdef : (SchemaDeclBase)context.ElementDecl;
314 if (decl.Values != null && !decl.CheckEnumeration(typedValue)) {
315 if (dtype.TokenizedType == XmlTokenizedType.NOTATION) {
316 SendValidationEvent(Res.Sch_NotationValue, typedValue.ToString());
319 SendValidationEvent(Res.Sch_EnumerationValue, typedValue.ToString());
323 if (!decl.CheckValue(typedValue)) {
325 SendValidationEvent(Res.Sch_FixedAttributeValue, attdef.Name.ToString());
328 SendValidationEvent(Res.Sch_FixedElementValue, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace));
332 catch (XmlSchemaException) {
333 if (attdef != null) {
334 SendValidationEvent(Res.Sch_AttributeValueDataType, attdef.Name.ToString());
337 SendValidationEvent(Res.Sch_ElementValueDataType, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace));
343 internal void AddID(string name, object node) {
344 // Note: It used to be true that we only called this if _fValidate was true,
345 // but due to the fact that you can now dynamically type somethign as an ID
346 // that is no longer true.
348 IDs = new Hashtable();
354 public override object FindId(string name) {
355 return IDs == null ? null : IDs[name];
358 private bool GenEntity(XmlQualifiedName qname) {
359 string n = qname.Name;
360 if (n[0] == '#') { // char entity reference
363 else if (SchemaEntity.IsPredefinedEntity(n)) {
367 SchemaEntity en = GetEntity(qname, false);
369 // well-formness error, see xml spec [68]
370 throw new XmlException(Res.Xml_UndeclaredEntity, n);
372 if (!en.NData.IsEmpty) {
373 // well-formness error, see xml spec [68]
374 throw new XmlException(Res.Xml_UnparsedEntityRef, n);
377 if (reader.StandAlone && en.DeclaredInExternal) {
378 SendValidationEvent(Res.Sch_StandAlone);
385 private SchemaEntity GetEntity(XmlQualifiedName qname, bool fParameterEntity) {
387 if (fParameterEntity) {
388 if (schemaInfo.ParameterEntities.TryGetValue(qname, out entity)) {
393 if (schemaInfo.GeneralEntities.TryGetValue(qname, out entity)) {
400 private void CheckForwardRefs() {
401 IdRefNode next = idRefListHead;
402 while (next != null) {
403 if(FindId(next.Id) == null) {
404 SendValidationEvent(new XmlSchemaException(Res.Sch_UndeclaredId, next.Id, reader.BaseURI, next.LineNo, next.LinePos));
406 IdRefNode ptr = next.Next;
407 next.Next = null; // unhook each object so it is cleaned up by Garbage Collector
410 // not needed any more.
411 idRefListHead = null;
414 private void Push(XmlQualifiedName elementName) {
415 context = (ValidationState)validationStack.Push();
416 if (context == null) {
417 context = new ValidationState();
418 validationStack.AddToTop(context);
420 context.LocalName = elementName.Name;
421 context.Namespace = elementName.Namespace;
422 context.HasMatched = false;
423 context.IsNill = false;
424 context.NeedValidateChildren = false;
428 if (validationStack.Length > 1) {
429 validationStack.Pop();
430 context = (ValidationState)validationStack.Peek();
436 public static void SetDefaultTypedValue(
438 IDtdParserAdapter readerAdapter
441 string value = attdef.DefaultValueExpanded;
442 XmlSchemaDatatype dtype = attdef.Datatype;
444 return; // no reason to check
446 if (dtype.TokenizedType != XmlTokenizedType.CDATA) {
447 value = value.Trim();
449 attdef.DefaultValueTyped = dtype.ParseValue(value, readerAdapter.NameTable, readerAdapter.NamespaceResolver);
452 catch (XmlSchemaException ex) {
453 Debug.WriteLineIf(DiagnosticsSwitches.XmlSchema.TraceError, ex.Message);
457 IValidationEventHandling eventHandling = ((IDtdParserAdapterWithValidation)readerAdapter).ValidationEventHandling;
458 if (eventHandling != null) {
459 XmlSchemaException e = new XmlSchemaException(Res.Sch_AttributeDefaultDataType, attdef.Name.ToString());
460 eventHandling.SendEvent(e, XmlSeverityType.Error);
465 public static void CheckDefaultValue(
468 IValidationEventHandling eventHandling,
472 if (baseUriStr == null) {
473 baseUriStr = string.Empty;
475 XmlSchemaDatatype dtype = attdef.Datatype;
477 return; // no reason to check
479 object typedValue = attdef.DefaultValueTyped;
481 // Check special types
482 XmlTokenizedType ttype = dtype.TokenizedType;
483 if (ttype == XmlTokenizedType.ENTITY) {
484 if (dtype.Variety == XmlSchemaDatatypeVariety.List) {
485 string[] ss = (string[])typedValue;
486 for (int i = 0; i < ss.Length; ++i) {
487 ProcessEntity(sinfo, ss[i], eventHandling, baseUriStr, attdef.ValueLineNumber, attdef.ValueLinePosition);
491 ProcessEntity(sinfo, (string)typedValue, eventHandling, baseUriStr, attdef.ValueLineNumber, attdef.ValueLinePosition);
494 else if (ttype == XmlTokenizedType.ENUMERATION) {
495 if (!attdef.CheckEnumeration(typedValue)) {
496 if (eventHandling != null) {
497 XmlSchemaException e = new XmlSchemaException(Res.Sch_EnumerationValue, typedValue.ToString(), baseUriStr, attdef.ValueLineNumber, attdef.ValueLinePosition);
498 eventHandling.SendEvent(e, XmlSeverityType.Error);
504 catch (XmlSchemaException ex) {
505 Debug.WriteLineIf(DiagnosticsSwitches.XmlSchema.TraceError, ex.Message);
510 if (eventHandling != null) {
511 XmlSchemaException e = new XmlSchemaException(Res.Sch_AttributeDefaultDataType, attdef.Name.ToString());
512 eventHandling.SendEvent(e, XmlSeverityType.Error);
517 #pragma warning restore 618