Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Xml / System / Xml / Schema / DtdValidator.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="DtdValidator.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7
8 namespace System.Xml.Schema {
9     using System;
10     using System.Collections;
11     using System.Text;
12     using System.IO;
13     using System.Net;
14     using System.Diagnostics;
15     using System.Xml.Schema;
16     using System.Xml.XPath;
17
18 #pragma warning disable 618
19
20     internal sealed class DtdValidator : BaseValidator {
21
22         //required by ParseValue
23         class NamespaceManager : XmlNamespaceManager {
24             public override string LookupNamespace(string prefix) { return prefix; }
25         }
26         
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;
32         Hashtable        IDs;
33         IdRefNode        idRefListHead;
34         bool             processIdentityConstraints;
35         
36         internal DtdValidator(XmlValidatingReaderImpl reader, IValidationEventHandling eventHandling, bool processIdentityConstraints)  : base(reader, null, eventHandling) {
37             this.processIdentityConstraints = processIdentityConstraints;
38             Init();
39         }
40
41         private void Init() {
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;
49             Push(name);
50         }
51         
52         public override void Validate() {
53             if (schemaInfo.SchemaType == SchemaType.DTD) {
54                 switch (reader.NodeType) {
55                         case XmlNodeType.Element:
56                             ValidateElement();
57                             if (reader.IsEmptyElement) {
58                                 goto case XmlNodeType.EndElement;
59                             }
60                             break;
61                         case XmlNodeType.Whitespace:
62                         case XmlNodeType.SignificantWhitespace:
63                             if (MeetsStandAloneConstraint()) {
64                                 ValidateWhitespace();
65                             }
66                             break;
67                         case XmlNodeType.ProcessingInstruction:
68                         case XmlNodeType.Comment:
69                             ValidatePIComment();
70                             break;
71
72                         case XmlNodeType.Text:          // text inside a node
73                         case XmlNodeType.CDATA:         // <![CDATA[...]]>
74                             ValidateText();
75                             break;
76                         case XmlNodeType.EntityReference:
77                             if (!GenEntity( new XmlQualifiedName(reader.LocalName, reader.Prefix) ) ){
78                                 ValidateText();
79                             }
80                             break;
81                         case XmlNodeType.EndElement:
82                             ValidateEndElement();
83                             break;
84                 }
85             }
86              else {
87                 if(reader.Depth == 0 && 
88                     reader.NodeType == XmlNodeType.Element) {
89                     SendValidationEvent(Res.Xml_NoDTDPresent, this.name.ToString(), XmlSeverityType.Warning);
90                 }
91             }
92         }
93
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);
100                  return false;
101             }
102             return true;
103         }
104         
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);
110                 }
111                 
112             }
113         }
114
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);
121                 }
122             else {
123                 ValidateChildElement();
124             }
125             ProcessElement();
126         }
127
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
131                 int errorCode = 0;
132                 context.ElementDecl.ContentValidator.ValidateElement(elementName, context, out errorCode);
133                 if (errorCode < 0) {
134                     XmlSchemaValidator.ElementValidationError(elementName, context, EventHandler, reader, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition, null);
135                 }
136             }
137         }
138
139         private void ValidateStartElement() {
140             if (context.ElementDecl != null) {
141                 Reader.SchemaTypeObject =  context.ElementDecl.SchemaType;
142
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??
146                 }
147                 if ( context.ElementDecl.HasRequiredAttribute ) {
148                     attPresence.Clear();
149                 }
150             }
151             
152             if (Reader.MoveToFirstAttribute()) {
153                 do {
154                     try {
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);
160                             }
161                             Reader.SchemaTypeObject = attnDef.SchemaType;
162                             
163                             if (attnDef.Datatype != null && !reader.IsDefault) { //Since XmlTextReader adds default attributes, do not check again
164                                 // set typed value
165                                 CheckValue(Reader.Value, attnDef);
166                             }
167                         }
168                         else {
169                             SendValidationEvent(Res.Sch_UndeclaredAttribute, reader.Name);
170                         }
171                     }
172                     catch (XmlSchemaException e) {
173                         e.SetSource(Reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition);
174                         SendValidationEvent(e);
175                     }
176                 } while(Reader.MoveToNextAttribute());
177                 Reader.MoveToElement();
178             }
179             
180         }
181
182         private void ValidateEndStartElement() {
183             if (context.ElementDecl.HasRequiredAttribute) {
184                 try {
185                     context.ElementDecl.CheckAttributes(attPresence, Reader.StandAlone);
186                 }
187                 catch (XmlSchemaException e) {
188                     e.SetSource(Reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition);
189                     SendValidationEvent(e);
190                 }
191             }
192             
193             if (context.ElementDecl.Datatype != null) {
194                 checkDatatype = true;
195                 hasSibling = false;
196                 textString = string.Empty;
197                 textValue.Length = 0;
198             }
199         }
200
201         private void ProcessElement() {
202             SchemaElementDecl elementDecl = schemaInfo.GetElementDecl(elementName);
203             Push(elementName);
204             if (elementDecl != null) {
205                 context.ElementDecl = elementDecl;
206                 ValidateStartElement();
207                 ValidateEndStartElement();
208                 context.NeedValidateChildren = true;
209                 elementDecl.ContentValidator.InitValidation( context );
210             }
211             else {
212                 SendValidationEvent(Res.Sch_UndeclaredElement, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace));
213                 context.ElementDecl = null;
214             }
215         }
216
217         public override void CompleteValidation() {
218             if (schemaInfo.SchemaType == SchemaType.DTD) {
219                 do {
220                     ValidateEndElement();
221                 } while (Pop());
222                 CheckForwardRefs();
223             }
224         }
225
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);
231                     }
232                 }
233
234                 if (checkDatatype) {
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;
240                 }
241             }
242             Pop();
243
244         }
245                 
246         public override bool PreserveWhitespace { 
247             get { return context.ElementDecl != null ? context.ElementDecl.ContentValidator.PreserveWhitespace : false; }
248         }
249
250
251         void ProcessTokenizedType(
252             XmlTokenizedType    ttype,
253             string              name
254         ) {
255             switch(ttype) {
256             case XmlTokenizedType.ID:
257                 if (processIdentityConstraints) {
258                     if (FindId(name) != null) {
259                         SendValidationEvent(Res.Sch_DupId, name);
260                     }
261                     else {
262                         AddID(name, context.LocalName);
263                     }
264                 }    
265                 break;
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);
271                     }
272                 }
273                 
274                 break;
275             case XmlTokenizedType.ENTITY:
276                 ProcessEntity(schemaInfo, name, this, EventHandler, Reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition);
277                 break;
278             default:
279                 break;
280             }
281         }
282
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) {
285             try {
286                 reader.TypedValueObject = null;
287                 bool isAttn = attdef != null;
288                 XmlSchemaDatatype dtype = isAttn ? attdef.Datatype : context.ElementDecl.Datatype;
289                 if (dtype == null) {
290                     return; // no reason to check
291                 }
292                 
293                 if (dtype.TokenizedType != XmlTokenizedType.CDATA) {
294                     value = value.Trim();
295                 }
296
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]);
306                         }
307                     }
308                     else {
309                         ProcessTokenizedType(dtype.TokenizedType, (string)typedValue);
310                     }
311                 }
312
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());
317                     }
318                     else {
319                         SendValidationEvent(Res.Sch_EnumerationValue, typedValue.ToString());
320                     }
321
322                 }
323                 if (!decl.CheckValue(typedValue)) {
324                     if (isAttn) {
325                         SendValidationEvent(Res.Sch_FixedAttributeValue, attdef.Name.ToString());
326                     }
327                     else {
328                         SendValidationEvent(Res.Sch_FixedElementValue, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace));
329                     }
330                 }
331             }
332             catch (XmlSchemaException) {
333                 if (attdef != null) {
334                     SendValidationEvent(Res.Sch_AttributeValueDataType, attdef.Name.ToString());
335                 }
336                 else {
337                     SendValidationEvent(Res.Sch_ElementValueDataType, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace));
338                 }
339             }
340         }
341
342
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.
347             if (IDs == null) {
348                 IDs = new Hashtable();
349             }
350
351             IDs.Add(name, node);
352         }
353
354         public override object  FindId(string name) {
355             return IDs == null ? null : IDs[name];
356         }
357
358         private bool GenEntity(XmlQualifiedName qname) {
359             string n = qname.Name;
360             if (n[0] == '#') { // char entity reference
361                 return false;
362             }
363             else if (SchemaEntity.IsPredefinedEntity(n)) {
364                 return false;
365             }
366             else {
367                 SchemaEntity en = GetEntity(qname, false);
368                 if (en == null) {
369                     // well-formness error, see xml spec [68]
370                     throw new XmlException(Res.Xml_UndeclaredEntity, n); 
371                 }
372                 if (!en.NData.IsEmpty) {
373                     // well-formness error, see xml spec [68]
374                     throw new XmlException(Res.Xml_UnparsedEntityRef, n); 
375                 }
376
377                 if (reader.StandAlone && en.DeclaredInExternal) {
378                     SendValidationEvent(Res.Sch_StandAlone);    
379                 }
380                 return true;
381             }
382         }
383
384
385         private SchemaEntity GetEntity(XmlQualifiedName qname, bool fParameterEntity) {
386             SchemaEntity entity;
387             if (fParameterEntity) {
388                 if (schemaInfo.ParameterEntities.TryGetValue(qname, out entity)) {
389                     return entity;
390                 }
391             }
392             else {
393                 if (schemaInfo.GeneralEntities.TryGetValue(qname, out entity)) {
394                     return entity;
395                 }
396             }
397             return null;
398         }
399
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));
405                 }
406                 IdRefNode ptr = next.Next;
407                 next.Next = null; // unhook each object so it is cleaned up by Garbage Collector
408                 next = ptr;
409             }
410             // not needed any more.
411             idRefListHead = null;
412         }
413
414          private void Push(XmlQualifiedName elementName) {
415             context = (ValidationState)validationStack.Push();
416             if (context == null) {
417                 context = new ValidationState();
418                 validationStack.AddToTop(context);
419             }
420             context.LocalName = elementName.Name;
421             context.Namespace = elementName.Namespace;
422             context.HasMatched = false;
423             context.IsNill = false;
424             context.NeedValidateChildren = false;
425          }
426
427         private bool Pop() {
428             if (validationStack.Length > 1) {
429                 validationStack.Pop();
430                 context = (ValidationState)validationStack.Peek();
431                 return true;
432             }
433             return false;
434         }
435         
436         public static void SetDefaultTypedValue(
437             SchemaAttDef        attdef,
438             IDtdParserAdapter   readerAdapter
439         ) {
440             try {
441                 string value = attdef.DefaultValueExpanded;
442                 XmlSchemaDatatype dtype = attdef.Datatype;
443                 if (dtype == null) {
444                     return; // no reason to check
445                 }
446                 if (dtype.TokenizedType != XmlTokenizedType.CDATA) {
447                     value = value.Trim();
448                 }
449                 attdef.DefaultValueTyped = dtype.ParseValue(value, readerAdapter.NameTable, readerAdapter.NamespaceResolver);
450             }
451 #if DEBUG
452             catch (XmlSchemaException ex) {
453                 Debug.WriteLineIf(DiagnosticsSwitches.XmlSchema.TraceError, ex.Message);
454 #else
455             catch (Exception)  {
456 #endif
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);
461                 }
462             }
463         }
464
465         public static void CheckDefaultValue(
466             SchemaAttDef        attdef,
467             SchemaInfo          sinfo,
468             IValidationEventHandling eventHandling,
469             string              baseUriStr
470         ) {
471             try {
472                 if (baseUriStr == null) {
473                     baseUriStr = string.Empty;
474                 }
475                 XmlSchemaDatatype dtype = attdef.Datatype;
476                 if (dtype == null) {
477                     return; // no reason to check
478                 }
479                 object typedValue = attdef.DefaultValueTyped;
480
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);
488                         }
489                     }
490                     else {
491                         ProcessEntity(sinfo, (string)typedValue, eventHandling, baseUriStr, attdef.ValueLineNumber, attdef.ValueLinePosition);
492                     }
493                 }
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);
499                         }
500                     }
501                 }
502             }
503 #if DEBUG
504             catch (XmlSchemaException ex) {
505                 Debug.WriteLineIf(DiagnosticsSwitches.XmlSchema.TraceError, ex.Message);
506 #else
507             catch (Exception)  {
508 #endif
509
510                 if (eventHandling != null) {
511                     XmlSchemaException e = new XmlSchemaException(Res.Sch_AttributeDefaultDataType, attdef.Name.ToString());
512                     eventHandling.SendEvent(e, XmlSeverityType.Error);
513                 }
514             }
515         }
516     }
517 #pragma warning restore 618
518
519 }
520