New test.
[mono.git] / mcs / class / System.XML / System.Xml / DTDValidatingReader.cs
1 //
2 // DTDValidatingReader.cs
3 //
4 // Author:
5 //   Atsushi Enomoto  (ginga@kit.hi-ho.ne.jp)
6 //
7 // (C)2003 Atsushi Enomoto
8 // (C)2004 Novell Inc.
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31 using System;
32 using System.Collections;
33 #if NET_2_0
34 using System.Collections.Generic;
35 #endif
36 using System.IO;
37 using System.Text;
38 using System.Xml;
39 using System.Xml.Schema;
40
41 #if NET_2_0
42 using XmlTextReaderImpl = Mono.Xml2.XmlTextReader;
43 #else
44 using XmlTextReaderImpl = System.Xml.XmlTextReader;
45 #endif
46
47
48 namespace Mono.Xml
49 {
50 #if NET_2_0
51         internal class DTDValidatingReader : XmlReader, IXmlLineInfo, IHasXmlParserContext, IHasXmlSchemaInfo, IXmlNamespaceResolver
52 #else
53         internal class DTDValidatingReader : XmlReader, IXmlLineInfo, IHasXmlParserContext, IHasXmlSchemaInfo
54 #endif
55         {
56                 public DTDValidatingReader (XmlReader reader)
57                         : this (reader, null)
58                 {
59                 }
60
61                 internal DTDValidatingReader (XmlReader reader,
62                         XmlValidatingReader validatingReader)
63                 {
64                         entityReaderStack = new Stack ();
65                         entityReaderDepthStack = new Stack ();
66                         this.reader = reader;
67                         this.sourceTextReader = reader as XmlTextReader;
68                         elementStack = new Stack ();
69                         automataStack = new Stack ();
70                         attributes = new ArrayList ();
71                         attributeValues = new Hashtable ();
72                         attributeLocalNames = new Hashtable ();
73                         attributeNamespaces = new Hashtable ();
74                         attributePrefixes = new Hashtable ();
75                         nsmgr = new XmlNamespaceManager (reader.NameTable);
76                         this.validatingReader = validatingReader;
77                         valueBuilder = new StringBuilder ();
78                         idList = new ArrayList ();
79                         missingIDReferences = new ArrayList ();
80                         XmlTextReader xtReader = reader as XmlTextReader;
81                         if (xtReader != null) {
82                                 resolver = xtReader.Resolver;
83                         }
84                         else
85                                 resolver = new XmlUrlResolver ();
86                 }
87
88                 Stack entityReaderStack;
89                 Stack entityReaderDepthStack;
90                 XmlReader reader;
91                 XmlTextReader sourceTextReader;
92                 XmlTextReader nextEntityReader;
93                 DTDObjectModel dtd;
94                 Stack elementStack;
95                 Stack automataStack;
96                 string currentElement;
97                 string currentAttribute;
98                 string currentTextValue;
99                 string constructingTextValue;
100                 bool shouldResetCurrentTextValue;
101                 bool consumedAttribute;
102                 bool insideContent;
103                 DTDAutomata currentAutomata;
104                 DTDAutomata previousAutomata;
105                 bool isStandalone;
106                 ArrayList attributes;
107                 Hashtable attributeValues;
108                 Hashtable attributeLocalNames;
109                 Hashtable attributeNamespaces;
110                 Hashtable attributePrefixes;
111                 XmlNamespaceManager nsmgr;
112                 StringBuilder valueBuilder;
113                 ArrayList idList;
114                 ArrayList missingIDReferences;
115                 XmlResolver resolver;
116                 EntityHandling currentEntityHandling;
117                 bool isSignificantWhitespace;
118                 bool isWhitespace;
119                 bool isText;
120                 bool dontResetTextType;
121                 bool popScope;
122
123                 // This field is used to get properties and to raise events.
124                 XmlValidatingReader validatingReader;
125
126                 public DTDObjectModel DTD {
127                         get { return dtd; }
128                 }
129
130                 public EntityHandling EntityHandling {
131                         get { return currentEntityHandling; }
132                         set { currentEntityHandling = value; }
133                 }
134
135                 public override void Close ()
136                 {
137                         reader.Close ();
138                 }
139
140                 // We had already done attribute validation, so can ignore name.
141                 public override string GetAttribute (int i)
142                 {
143                         if (currentTextValue != null)
144                                 throw new IndexOutOfRangeException ("Specified index is out of range: " + i);
145
146                         if (dtd == null)
147                                 return reader.GetAttribute (i);
148
149                         if (attributes.Count <= i)
150                                 throw new IndexOutOfRangeException ("Specified index is out of range: " + i);
151
152                         string attrName = (string) attributes [i];
153                         return FilterNormalization (attrName, (string) attributeValues [attrName]);
154                 }
155
156                 public override string GetAttribute (string name)
157                 {
158                         if (currentTextValue != null)
159                                 return null;
160
161                         if (dtd == null)
162                                 return reader.GetAttribute (name);
163
164                         return FilterNormalization (name, (string) attributeValues [name]);
165                 }
166
167                 public override string GetAttribute (string name, string ns)
168                 {
169                         if (currentTextValue != null)
170                                 return null;
171
172                         if (dtd == null)
173                                 return reader.GetAttribute (name, ns);
174
175                         return reader.GetAttribute ((string) attributeLocalNames [name], ns);
176                 }
177
178 #if NET_2_0
179                 IDictionary<string, string> IXmlNamespaceResolver.GetNamespacesInScope (XmlNamespaceScope scope)
180                 {
181                         IXmlNamespaceResolver res = reader as IXmlNamespaceResolver;
182                         return res != null ? res.GetNamespacesInScope (scope) : new Dictionary<string, string> ();
183                 }
184 #endif
185
186                 bool IXmlLineInfo.HasLineInfo ()
187                 {
188                         IXmlLineInfo ixli = reader as IXmlLineInfo;
189                         if (ixli != null)
190                                 return ixli.HasLineInfo ();
191                         else
192                                 return false;
193                 }
194
195                 public override string LookupNamespace (string prefix)
196                 {
197                         string s = nsmgr.LookupNamespace (NameTable.Get (prefix));
198                         return s == String.Empty ? null : s;
199                 }
200
201 #if NET_2_0
202                 string IXmlNamespaceResolver.LookupPrefix (string ns)
203                 {
204                         IXmlNamespaceResolver res = reader as IXmlNamespaceResolver;
205                         return res != null ? res.LookupPrefix (ns) : null;
206                 }
207 #endif
208
209                 public override void MoveToAttribute (int i)
210                 {
211                         if (currentTextValue != null)
212                                 throw new IndexOutOfRangeException ("The index is out of range.");
213
214                         if (dtd == null) {
215                                 reader.MoveToAttribute (i);
216                                 currentAttribute = reader.Name;
217                                 consumedAttribute = false;
218                                 return;
219                         }
220
221                         if (currentElement == null)
222                                 throw new IndexOutOfRangeException ("The index is out of range.");
223
224                         if (attributes.Count > i) {
225                                 if (reader.AttributeCount > i)
226                                         reader.MoveToAttribute (i);
227                                 currentAttribute = (string) attributes [i];
228                                 consumedAttribute = false;
229                                 return;
230                         } else
231                                 throw new IndexOutOfRangeException ("The index is out of range.");
232                 }
233
234                 public override bool MoveToAttribute (string name)
235                 {
236                         if (currentTextValue != null)
237                                 return false;
238
239                         if (dtd == null) {
240                                 bool b = reader.MoveToAttribute (name);
241                                 if (b) {
242                                         currentAttribute = reader.Name;
243                                         consumedAttribute = false;
244                                 }
245                                 return b;
246                         }
247
248                         if (currentElement == null)
249                                 return false;
250
251                         int idx = attributes.IndexOf (name);
252                         if (idx >= 0) {
253                                 currentAttribute = name;
254                                 consumedAttribute = false;
255                                 return true;
256                         }
257                         return false;
258                 }
259
260                 public override bool MoveToAttribute (string name, string ns)
261                 {
262                         if (currentTextValue != null)
263                                 return false;
264
265                         if (dtd == null) {
266                                 bool b = reader.MoveToAttribute (name, ns);
267                                 if (b) {
268                                         currentAttribute = reader.Name;
269                                         consumedAttribute = false;
270                                 }
271                                 return b;
272                         }
273
274                         if (reader.MoveToAttribute (name, ns)) {
275                                 currentAttribute = reader.Name;
276                                 consumedAttribute = false;
277                                 return true;
278                         }
279
280                         for (int i = 0; i < attributes.Count; i++) {
281                                 string iter = (string) attributes [i];
282                                 if ((string) attributeLocalNames [iter] == name)
283                                         return MoveToAttribute (iter);
284                         }
285                         return false;
286                 }
287
288                 public override bool MoveToElement ()
289                 {
290                         if (currentTextValue != null)
291                                 return false;
292
293                         bool b = reader.MoveToElement ();
294                         if (!b && !IsDefault)
295                                 return false;
296                         currentAttribute = null;
297                         consumedAttribute = false;
298                         return true;
299                 }
300
301                 public override bool MoveToFirstAttribute ()
302                 {
303                         if (currentTextValue != null)
304                                 return false;
305
306                         if (dtd == null) {
307                                 bool b = reader.MoveToFirstAttribute ();
308                                 if (b) {
309                                         currentAttribute = reader.Name;
310                                         consumedAttribute = false;
311                                 }
312                                 return b;
313                         }
314
315                         if (attributes.Count == 0)
316                                 return false;
317                         currentAttribute = (string) attributes [0];
318                         reader.MoveToAttribute (currentAttribute);
319                         consumedAttribute = false;
320                         return true;
321                 }
322
323                 public override bool MoveToNextAttribute ()
324                 {
325                         if (currentTextValue != null)
326                                 return false;
327
328                         if (dtd == null) {
329                                 bool b = reader.MoveToNextAttribute ();
330                                 if (b) {
331                                         currentAttribute = reader.Name;
332                                         consumedAttribute = false;
333                                 }
334                                 return b;
335                         }
336
337                         if (currentAttribute == null)
338                                 return MoveToFirstAttribute ();
339
340                         int idx = attributes.IndexOf (currentAttribute);
341                         if (idx + 1 < attributes.Count) {
342                                 currentAttribute = (string) attributes [idx + 1];
343                                 reader.MoveToAttribute (currentAttribute);
344                                 consumedAttribute = false;
345                                 return true;
346                         } else
347                                 return false;
348                 }
349
350                 /*
351                 private void OnValidationEvent (object o, ValidationEventArgs e)
352                 {
353                         this.HandleError (e.Exception, e.Severity);
354                 }
355                 */
356
357                 public override bool Read ()
358                 {
359                         if (currentTextValue != null)
360                                 shouldResetCurrentTextValue = true;
361
362                         MoveToElement ();
363
364                         currentElement = null;
365                         currentAttribute = null;
366                         consumedAttribute = false;
367                         attributes.Clear ();
368                         attributeLocalNames.Clear ();
369                         attributeValues.Clear ();
370                         attributeNamespaces.Clear ();
371                         attributePrefixes.Clear ();
372                         isWhitespace = false;
373                         isSignificantWhitespace = false;
374                         isText = false;
375                         dontResetTextType = false;
376
377                         bool b = ReadContent () || currentTextValue != null;
378                         if (!b && this.missingIDReferences.Count > 0) {
379                                 this.HandleError ("Missing ID reference was found: " +
380                                         String.Join (",", missingIDReferences.ToArray (typeof (string)) as string []),
381                                         XmlSeverityType.Error);
382                                 // Don't output the same errors so many times.
383                                 this.missingIDReferences.Clear ();
384                         }
385                         if (validatingReader != null)
386                                 EntityHandling = validatingReader.EntityHandling;
387                         return b;
388                 }
389
390                 private bool ReadContent ()
391                 {
392                         if (popScope) {
393                                 nsmgr.PopScope ();
394                                 popScope = false;
395                         }
396                         if (nextEntityReader != null) {
397                                 if (DTD == null || DTD.EntityDecls [reader.Name] == null)
398                                         throw NotWFError (String.Format ("Entity '{0}' was not declared.", reader.Name));
399                                 entityReaderStack.Push (reader);
400                                 entityReaderDepthStack.Push (Depth);
401                                 reader = sourceTextReader = nextEntityReader;
402                                 nextEntityReader = null;
403                                 return ReadContent ();
404                         } else if (reader.EOF && entityReaderStack.Count > 0) {
405                                 reader.Close ();
406                                 reader = entityReaderStack.Pop () as XmlReader;
407                                 entityReaderDepthStack.Pop ();
408                                 sourceTextReader = reader as XmlTextReader;
409                                 return ReadContent ();
410                         }
411
412                         bool b = !reader.EOF;
413                         if (shouldResetCurrentTextValue) {
414                                 currentTextValue = null;
415                                 shouldResetCurrentTextValue = false;
416                         }
417                         else
418                                 b = reader.Read ();
419
420                         if (!insideContent && reader.NodeType == XmlNodeType.Element) {
421                                 insideContent = true;
422                                 if (dtd == null)
423                                         currentAutomata = null;
424                                 else
425                                         currentAutomata = dtd.RootAutomata;
426                         }
427
428                         if (!b) {
429                                 if (entityReaderStack.Count > 0) {
430                                         if (validatingReader.EntityHandling == EntityHandling.ExpandEntities)
431                                                 return ReadContent ();
432                                         else
433                                                 return true;    // EndEntity
434                                 }
435
436                                 if (elementStack.Count != 0)
437                                         throw new InvalidOperationException ("Unexpected end of XmlReader.");
438                                 return false;
439                         }
440
441                         DTDElementDeclaration elem = null;
442
443                         switch (reader.NodeType) {
444                         case XmlNodeType.XmlDeclaration:
445                                 if (GetAttribute ("standalone") == "yes")
446                                         isStandalone = true;
447                                 ValidateAttributes (null, false);
448                                 break;
449
450                         case XmlNodeType.DocumentType:
451 //                              XmlTextReader xmlTextReader = reader as XmlTextReader;
452                                 IHasXmlParserContext ctx = reader as IHasXmlParserContext;
453                                 if (ctx != null)
454                                         dtd = ctx.ParserContext.Dtd;
455                                 if (dtd == null) {
456                                         XmlTextReaderImpl xmlTextReader = new XmlTextReaderImpl ("", XmlNodeType.Document, null);
457                                         xmlTextReader.XmlResolver = resolver;
458                                         xmlTextReader.GenerateDTDObjectModel (reader.Name,
459                                                 reader ["PUBLIC"], reader ["SYSTEM"], reader.Value);
460                                         dtd = xmlTextReader.DTD;
461                                 }
462
463                                 // Validity Constraints Check.
464                                 if (DTD.Errors.Length > 0)
465                                         for (int i = 0; i < DTD.Errors.Length; i++)
466                                                 HandleError (DTD.Errors [i].Message, XmlSeverityType.Error);
467
468                                 // NData target exists.
469                                 foreach (DTDEntityDeclaration ent in dtd.EntityDecls.Values)
470                                         if (ent.NotationName != null && dtd.NotationDecls [ent.NotationName] == null)
471                                                 this.HandleError ("Target notation was not found for NData in entity declaration " + ent.Name + ".",
472                                                         XmlSeverityType.Error);
473                                 // NOTATION exists for attribute default values
474                                 foreach (DTDAttListDeclaration attListIter in dtd.AttListDecls.Values)
475                                         foreach (DTDAttributeDefinition def in attListIter.Definitions)
476                                                 if (def.Datatype.TokenizedType == XmlTokenizedType.NOTATION) {
477                                                         foreach (string notation in def.EnumeratedNotations)
478                                                                 if (dtd.NotationDecls [notation] == null)
479                                                                         this.HandleError ("Target notation was not found for NOTATION typed attribute default " + def.Name + ".",
480                                                                                 XmlSeverityType.Error);
481                                                 }
482
483                                 break;
484
485                         case XmlNodeType.Element:
486                                 nsmgr.PushScope ();
487                                 popScope = reader.IsEmptyElement;
488                                 if (constructingTextValue != null) {
489                                         currentTextValue = constructingTextValue;
490                                         constructingTextValue = null;
491                                         if (isWhitespace)
492                                                 ValidateWhitespaceNode ();
493                                         return true;
494                                 }
495                                 elementStack.Push (reader.Name);
496                                 // startElementDeriv
497                                 // If no schema specification, then skip validation.
498                                 if (currentAutomata == null) {
499                                         ValidateAttributes (null, false);
500                                         if (reader.IsEmptyElement)
501                                                 goto case XmlNodeType.EndElement;
502                                         break;
503                                 }
504
505                                 previousAutomata = currentAutomata;
506                                 currentAutomata = currentAutomata.TryStartElement (reader.Name);
507                                 if (currentAutomata == DTD.Invalid) {
508                                         HandleError (String.Format ("Invalid start element found: {0}", reader.Name),
509                                                 XmlSeverityType.Error);
510                                         currentAutomata = previousAutomata;
511                                 }
512                                 elem = DTD.ElementDecls [reader.Name];
513                                 if (elem == null) {
514                                         HandleError (String.Format ("Element {0} is not declared.", reader.Name),
515                                                 XmlSeverityType.Error);
516                                         currentAutomata = previousAutomata;
517                                 }
518
519                                 currentElement = Name;
520                                 automataStack.Push (currentAutomata);
521                                 if (elem != null)       // i.e. not invalid
522                                         currentAutomata = elem.ContentModel.GetAutomata ();
523
524                                 DTDAttListDeclaration attList = dtd.AttListDecls [currentElement];
525                                 if (attList != null) {
526                                         // check attributes
527                                         ValidateAttributes (attList, true);
528                                         currentAttribute = null;
529                                 } else {
530                                         if (reader.HasAttributes) {
531                                                 HandleError (String.Format (
532                                                         "Attributes are found on element {0} while it has no attribute definitions.", currentElement),
533                                                         XmlSeverityType.Error);
534                                         }
535                                         // SetupValidityIgnorantAttributes ();
536                                         ValidateAttributes (null, false);
537                                 }
538                                 // If it is empty element then directly check end element.
539                                 if (reader.IsEmptyElement)
540                                         goto case XmlNodeType.EndElement;
541                                 break;
542
543                         case XmlNodeType.EndElement:
544                                 if (constructingTextValue != null) {
545                                         currentTextValue = constructingTextValue;
546                                         constructingTextValue = null;
547                                         return true;
548                                 }
549                                 popScope = true;
550                                 elementStack.Pop ();
551                                 // endElementDeriv
552                                 // If no schema specification, then skip validation.
553                                 if (currentAutomata == null)
554                                         break;
555
556                                 elem = DTD.ElementDecls [reader.Name];
557                                 if (elem == null) {
558                                         HandleError (String.Format ("Element {0} is not declared.", reader.Name),
559                                                 XmlSeverityType.Error);
560                                 }
561
562                                 previousAutomata = currentAutomata;
563                                 // Don't let currentAutomata
564                                 DTDAutomata tmpAutomata = currentAutomata.TryEndElement ();
565                                 if (tmpAutomata == DTD.Invalid) {
566                                         HandleError (String.Format ("Invalid end element found: {0}", reader.Name),
567                                                 XmlSeverityType.Error);
568                                         currentAutomata = previousAutomata;
569                                 }
570
571                                 currentAutomata = automataStack.Pop () as DTDAutomata;
572                                 break;
573
574                         case XmlNodeType.CDATA:
575                                 isSignificantWhitespace = isWhitespace = false;
576                                 isText = true;
577
578                                 ValidateText ();
579
580                                 if (currentTextValue != null) {
581                                         currentTextValue = constructingTextValue;
582                                         constructingTextValue = null;
583                                         return true;
584                                 }
585                                 break;
586                         case XmlNodeType.SignificantWhitespace:
587                                 if (!isText)
588                                         isSignificantWhitespace = true;
589                                 isWhitespace = false;
590                                 dontResetTextType = true;
591                                 goto case XmlNodeType.DocumentFragment;
592                         case XmlNodeType.Text:
593                                 isWhitespace = isSignificantWhitespace = false;
594                                 isText = true;
595                                 goto case XmlNodeType.DocumentFragment;
596                         case XmlNodeType.DocumentFragment:
597                                 // it should not happen, but in case if
598                                 // XmlReader really returns it, just ignore.
599                                 if (reader.NodeType == XmlNodeType.DocumentFragment)
600                                         break;
601
602                                 ValidateText ();
603
604                                 if (entityReaderStack.Count > 0 && validatingReader.EntityHandling == EntityHandling.ExpandEntities) {
605                                         constructingTextValue += reader.Value;
606                                         return ReadContent ();
607                                 }
608                                 break;
609                         case XmlNodeType.Whitespace:
610                                 if (!isText && !isSignificantWhitespace)
611                                         isWhitespace = true;
612                                 goto case XmlNodeType.DocumentFragment;
613                         case XmlNodeType.EntityReference:
614                                 if (validatingReader.EntityHandling == EntityHandling.ExpandEntities) {
615                                         ResolveEntity ();
616                                         return ReadContent ();
617                                 }
618                                 break;
619                         }
620                         if (isWhitespace)
621                                 ValidateWhitespaceNode ();
622                         currentTextValue = constructingTextValue;
623                         constructingTextValue = null;
624                         MoveToElement ();
625                         return true;
626                 }
627
628                 private void ValidateText ()
629                 {
630                         if (currentAutomata == null)
631                                 return;
632
633                         DTDElementDeclaration elem = null;
634                         if (elementStack.Count > 0)
635                                 elem = dtd.ElementDecls [elementStack.Peek () as string];
636                         // Here element should have been already validated, so
637                         // if no matching declaration is found, simply ignore.
638                         if (elem != null && !elem.IsMixedContent && !elem.IsAny && !isWhitespace) {
639                                 HandleError (String.Format ("Current element {0} does not allow character data content.", elementStack.Peek () as string),
640                                         XmlSeverityType.Error);
641                                 currentAutomata = previousAutomata;
642                         }
643                 }
644
645                 private void ValidateWhitespaceNode ()
646                 {
647                         // VC Standalone Document Declaration (2.9)
648                         if (this.isStandalone && DTD != null && elementStack.Count > 0) {
649                                 DTDElementDeclaration elem = DTD.ElementDecls [elementStack.Peek () as string];
650                                 if (elem != null && !elem.IsInternalSubset && !elem.IsMixedContent && !elem.IsAny && !elem.IsEmpty)
651                                         HandleError ("In standalone document, whitespace cannot appear in an element whose declaration explicitly contains child content model, not Mixed content.", XmlSeverityType.Error);
652                         }
653                 }
654
655                 private XmlException NotWFError (string message)
656                 {
657                         return new XmlException (this as IXmlLineInfo, BaseURI, message);
658                 }
659
660                 private void HandleError (string message, XmlSeverityType severity)
661                 {
662                         if (validatingReader != null &&
663                                 validatingReader.ValidationType == ValidationType.None)
664                                 return;
665
666                         IXmlLineInfo info = this as IXmlLineInfo;
667                         bool hasLine = info.HasLineInfo ();
668                         XmlSchemaException ex = new XmlSchemaException (
669                                 message,
670                                 hasLine ? info.LineNumber : 0,
671                                 hasLine ? info.LinePosition : 0, 
672                                 null,
673                                 BaseURI, 
674                                 null);
675                         HandleError (ex, severity);
676                 }
677
678                 private void HandleError (XmlSchemaException ex, XmlSeverityType severity)
679                 {
680                         if (validatingReader != null &&
681                                 validatingReader.ValidationType == ValidationType.None)
682                                 return;
683
684                         if (validatingReader != null)
685                                 this.validatingReader.OnValidationEvent (this,
686                                         new ValidationEventArgs (ex, ex.Message, severity));
687                         else if (severity == XmlSeverityType.Error)
688                                 throw ex;
689                 }
690
691                 Stack attributeValueEntityStack = new Stack ();
692
693                 private void ValidateAttributes (DTDAttListDeclaration decl, bool validate)
694                 {
695                         DtdValidateAttributes (decl, validate);
696
697                         foreach (string attr in attributes)
698                                 if (attr == "xmlns" ||
699                                         String.CompareOrdinal (attr, 0, "xmlns:", 0, 6) == 0)
700                                         nsmgr.AddNamespace (
701                                                 attr == "xmlns" ? String.Empty : (string) attributeLocalNames [attr],
702                                                 (string) attributeValues [attr]);
703
704                         foreach (string attr in attributes) {
705                                 string prefix = attr == "xmlns" ? "xmlns" : attributePrefixes [attr] as string;
706                                 if (prefix == String.Empty)
707                                         attributeNamespaces.Add (attr, String.Empty);
708                                 else
709                                         attributeNamespaces.Add (attr, LookupNamespace (prefix));
710                         }
711                 }
712
713                 private void DtdValidateAttributes (DTDAttListDeclaration decl, bool validate)
714                 {
715                         while (reader.MoveToNextAttribute ()) {
716                                 string attrName = reader.Name;
717                                 this.currentAttribute = attrName;
718                                 attributes.Add (attrName);
719                                 attributeLocalNames.Add (attrName, reader.LocalName);
720                                 attributePrefixes.Add (attrName, reader.Prefix);
721                                 XmlReader targetReader = reader;
722                                 string attrValue = null;
723                                 // It always resolves entity references on attributes (documented as such).
724 //                              if (currentEntityHandling == EntityHandling.ExpandCharEntities)
725 //                                      attrValue = reader.Value;
726 //                              else
727                                 {
728                                         while (attributeValueEntityStack.Count >= 0) {
729                                                 if (!targetReader.ReadAttributeValue ()) {
730                                                         if (attributeValueEntityStack.Count > 0) {
731                                                                 targetReader = attributeValueEntityStack.Pop () as XmlReader;
732                                                                 continue;
733                                                         } else
734                                                                 break;
735                                                 }
736                                                 switch (targetReader.NodeType) {
737                                                 case XmlNodeType.EntityReference:
738                                                         DTDEntityDeclaration edecl = DTD.EntityDecls [targetReader.Name];
739                                                         if (edecl == null) {
740                                                                 HandleError (String.Format ("Referenced entity {0} is not declared.", targetReader.Name),
741                                                                         XmlSeverityType.Error);
742                                                         } else {
743                                                                 XmlTextReader etr = new XmlTextReader (edecl.EntityValue, XmlNodeType.Attribute, ParserContext);
744                                                                 attributeValueEntityStack.Push (targetReader);
745                                                                 targetReader = etr;
746                                                                 continue;
747                                                         }
748                                                         break;
749                                                 case XmlNodeType.EndEntity:
750                                                         break;
751                                                 default:
752                                                         if (attrValue != null) {
753                                                                 valueBuilder.Append (attrValue);
754                                                                 attrValue = null;
755                                                         }
756                                                         
757                                                         if (valueBuilder.Length != 0)
758                                                                 valueBuilder.Append (targetReader.Value);
759                                                         else
760                                                                 attrValue = targetReader.Value;
761                                                         
762                                                         break;
763                                                 }
764                                         }
765                                         
766                                         if (attrValue == null) {
767                                                 attrValue = valueBuilder.ToString ();
768                                                 valueBuilder.Length = 0;
769                                         }
770                                 }
771                                 reader.MoveToElement ();
772                                 reader.MoveToAttribute (attrName);
773                                 attributeValues.Add (attrName, attrValue);
774
775                                 if (!validate)
776                                         continue;
777
778                                 // Validation
779
780                                 DTDAttributeDefinition def = decl [reader.Name];
781                                 if (def == null) {
782                                         HandleError (String.Format ("Attribute {0} is not declared.", reader.Name),
783                                                 XmlSeverityType.Error);
784                                         continue;
785                                 }
786
787                                 // check enumeration constraint
788                                 if (def.EnumeratedAttributeDeclaration.Count > 0)
789                                         if (!def.EnumeratedAttributeDeclaration.Contains (
790                                                 FilterNormalization (reader.Name, attrValue)))
791                                                 HandleError (String.Format ("Attribute enumeration constraint error in attribute {0}, value {1}.",
792                                                         reader.Name, attrValue), XmlSeverityType.Error);
793                                 if (def.EnumeratedNotations.Count > 0)
794                                         if (!def.EnumeratedNotations.Contains (
795                                                 FilterNormalization (reader.Name, attrValue)))
796                                                 HandleError (String.Format ("Attribute notation enumeration constraint error in attribute {0}, value {1}.",
797                                                         reader.Name, attrValue), XmlSeverityType.Error);
798
799                                 // check type constraint
800                                 string normalized = null;
801                                 if (def.Datatype != null)
802                                         normalized = FilterNormalization (def.Name, attrValue);
803                                 else
804                                         normalized = attrValue;
805                                 DTDEntityDeclaration ent;
806
807                                 // Common process to get list value
808                                 string [] list = null;
809                                 switch (def.Datatype.TokenizedType) {
810                                 case XmlTokenizedType.IDREFS:
811                                 case XmlTokenizedType.ENTITIES:
812                                 case XmlTokenizedType.NMTOKENS:
813                                         try {
814                                                 list = def.Datatype.ParseValue (normalized, NameTable, null) as string [];
815                                         } catch (Exception) {
816                                                 HandleError ("Attribute value is invalid against its data type.", XmlSeverityType.Error);
817                                                 list = new string [0];
818                                         }
819                                         break;
820                                 default:
821                                         try {
822                                                 def.Datatype.ParseValue (normalized, NameTable, null);
823                                         } catch (Exception ex) {
824                                                 HandleError (String.Format ("Attribute value is invalid against its data type '{0}'. {1}", def.Datatype, ex.Message), XmlSeverityType.Error);
825                                         }
826                                         break;
827                                 }
828
829                                 switch (def.Datatype.TokenizedType) {
830                                 case XmlTokenizedType.ID:
831                                         if (this.idList.Contains (normalized)) {
832                                                 HandleError (String.Format ("Node with ID {0} was already appeared.", attrValue),
833                                                         XmlSeverityType.Error);
834                                         } else {
835                                                 if (missingIDReferences.Contains (normalized))
836                                                         missingIDReferences.Remove (normalized);
837                                                 idList.Add (normalized);
838                                         }
839                                         break;
840                                 case XmlTokenizedType.IDREF:
841                                         if (!idList.Contains (normalized))
842                                                 missingIDReferences.Add (normalized);
843                                         break;
844                                 case XmlTokenizedType.IDREFS:
845                                         for (int i = 0; i < list.Length; i++) {
846                                                 string idref = list [i];
847                                                 if (!idList.Contains (idref))
848                                                         missingIDReferences.Add (idref);
849                                         }
850                                         break;
851                                 case XmlTokenizedType.ENTITY:
852                                         ent = dtd.EntityDecls [normalized];
853                                         if (ent == null)
854                                                 HandleError ("Reference to undeclared entity was found in attribute: " + reader.Name + ".", XmlSeverityType.Error);
855                                         else if (ent.NotationName == null)
856                                                 HandleError ("The entity specified by entity type value must be an unparsed entity. The entity definition has no NDATA in attribute: " + reader.Name + ".", XmlSeverityType.Error);
857                                         break;
858                                 case XmlTokenizedType.ENTITIES:
859                                         for (int i = 0; i < list.Length; i++) {
860                                                 string entref = list [i];
861                                                 ent = dtd.EntityDecls [FilterNormalization (reader.Name, entref)];
862                                                 if (ent == null)
863                                                         HandleError ("Reference to undeclared entity was found in attribute: " + reader.Name + ".", XmlSeverityType.Error);
864                                                 else if (ent.NotationName == null)
865                                                         HandleError ("The entity specified by ENTITIES type value must be an unparsed entity. The entity definition has no NDATA in attribute: " + reader.Name + ".", XmlSeverityType.Error);
866                                         }
867                                         break;
868 //                              case XmlTokenizedType.NMTOKEN: nothing to do
869 //                              case XmlTokenizedType.NMTOKENS: nothing to do
870                                 }
871
872                                 if (isStandalone && !def.IsInternalSubset && 
873                                         attrValue != normalized)
874                                         HandleError ("In standalone document, attribute value characters must not be checked against external definition.", XmlSeverityType.Error);
875
876                                 if (def.OccurenceType == 
877                                         DTDAttributeOccurenceType.Fixed &&
878                                         attrValue != def.DefaultValue)
879                                         HandleError (String.Format ("Fixed attribute {0} in element {1} has invalid value {2}.",
880                                                 def.Name, decl.Name, attrValue),
881                                                 XmlSeverityType.Error);
882                         }
883
884                         if (validate)
885                                 VerifyDeclaredAttributes (decl);
886
887                         MoveToElement ();
888                 }
889
890                 private void VerifyDeclaredAttributes (DTDAttListDeclaration decl)
891                 {
892                         // Check if all required attributes exist, and/or
893                         // if there is default values, then add them.
894                         for (int i = 0; i < decl.Definitions.Count; i++) {
895                                 DTDAttributeDefinition def = (DTDAttributeDefinition) decl.Definitions [i];
896                                 if (attributes.Contains (def.Name))
897                                         continue;
898
899                                 if (def.OccurenceType == DTDAttributeOccurenceType.Required) {
900                                         HandleError (String.Format ("Required attribute {0} in element {1} not found .",
901                                                 def.Name, decl.Name),
902                                                 XmlSeverityType.Error);
903                                         continue;
904                                 }
905
906                                 else if (def.DefaultValue == null)
907                                         continue;
908
909                                 if (this.isStandalone && !def.IsInternalSubset)
910                                         HandleError ("In standalone document, external default value definition must not be applied.", XmlSeverityType.Error);
911
912                                 switch (validatingReader.ValidationType) {
913                                 case ValidationType.Auto:
914                                         if (validatingReader.Schemas.Count == 0)
915                                                 goto case ValidationType.DTD;
916                                         break;
917                                 case ValidationType.DTD:
918                                 case ValidationType.None:
919                                         // Other than them, ignore DTD defaults.
920                                         attributes.Add (def.Name);
921                                         int colonAt = def.Name.IndexOf (':');
922                                         attributeLocalNames.Add (def.Name,
923                                                 colonAt < 0 ? def.Name :
924                                                 def.Name.Substring (colonAt + 1));
925                                         string prefix = colonAt < 0 ?
926                                                 String.Empty :
927                                                 def.Name.Substring (0, colonAt);
928                                         attributePrefixes.Add (def.Name, prefix);
929                                         attributeValues.Add (def.Name, def.DefaultValue);
930                                         break;
931                                 }
932                         }
933                 }
934
935                 public override bool ReadAttributeValue ()
936                 {
937                         if (consumedAttribute)
938                                 return false;
939                         if (NodeType == XmlNodeType.Attribute &&
940                                         currentEntityHandling == EntityHandling.ExpandEntities) {
941                                 consumedAttribute = true;
942                                 return true;
943                         }
944                         else if (IsDefault) {
945                                 consumedAttribute = true;
946                                 return true;
947                         }
948                         else
949                                 return reader.ReadAttributeValue ();
950                 }
951
952                 public override void ResolveEntity ()
953                 {
954                         if (resolver == null)
955                                 return;
956
957                         // "reader." is required since NodeType must not be entityref by nature.
958                         if (reader.NodeType != XmlNodeType.EntityReference)
959                                 throw new InvalidOperationException ("The current node is not an Entity Reference");
960                         DTDEntityDeclaration entity = DTD != null ? DTD.EntityDecls [reader.Name] as DTDEntityDeclaration : null;
961
962                         XmlNodeType xmlReaderNodeType =
963                                 (currentAttribute != null) ? XmlNodeType.Attribute : XmlNodeType.Element;
964
965 #if NET_2_0
966                         if (entity == null)
967                                         throw NotWFError (String.Format ("Reference to undeclared entity '{0}'.", reader.Name));
968 #endif
969
970                         // MS.NET 1.x ignores undeclared entity reference here..
971                         if (entity != null && entity.SystemId != null) {
972                                 Uri baseUri = entity.BaseURI == String.Empty ? null : new Uri (entity.BaseURI);
973                                 Stream stream = resolver.GetEntity (resolver.ResolveUri (baseUri, entity.SystemId), null, typeof (Stream)) as Stream;
974                                 nextEntityReader = new XmlTextReader (stream, xmlReaderNodeType, ParserContext);
975                         } else {
976                                 string replacementText =
977                                         (entity != null) ? entity.EntityValue : String.Empty;
978                                 nextEntityReader = new XmlTextReader (replacementText, xmlReaderNodeType, ParserContext);
979                         }
980                         nextEntityReader.XmlResolver = resolver;
981                 }
982
983                 public override int AttributeCount {
984                         get {
985                                 if (currentTextValue != null)
986                                         return 0;
987
988                                 if (dtd == null || !insideContent)
989                                         return reader.AttributeCount;
990
991                                 return attributes.Count;
992                         }
993                 }
994
995                 public override string BaseURI {
996                         get {
997                                 return reader.BaseURI;
998                         }
999                 }
1000
1001                 public override bool CanResolveEntity {
1002                         get { return true; }
1003                 }
1004
1005                 public override int Depth {
1006                         get {
1007                                 int baseNum = reader.Depth;
1008                                 if (entityReaderDepthStack.Count > 0) {
1009                                         baseNum += (int) entityReaderDepthStack.Peek ();
1010                                         if (NodeType != XmlNodeType.EndEntity)
1011                                                 baseNum++;
1012                                 }
1013                                 if (currentTextValue != null && reader.NodeType == XmlNodeType.EndElement)
1014                                         baseNum++;
1015
1016                                 return IsDefault ? baseNum + 1 : baseNum;
1017                         }
1018                 }
1019
1020                 public override bool EOF {
1021                         get { return reader.EOF && entityReaderStack.Count == 0; }
1022                 }
1023
1024                 public override bool HasValue {
1025                         get {
1026                                 return IsDefault ? true :
1027                                         currentTextValue != null ? true :
1028                                         reader.HasValue; }
1029                 }
1030
1031                 public override bool IsDefault {
1032                         get {
1033                                 if (currentTextValue != null)
1034                                         return false;
1035                                 if (currentAttribute == null)
1036                                         return false;
1037                                 return reader.GetAttribute (currentAttribute) == null;
1038                         }
1039                 }
1040
1041                 public override bool IsEmptyElement {
1042                         get {
1043                                 if (currentTextValue != null)
1044                                         return false;
1045                                 return reader.IsEmptyElement;
1046                         }
1047                 }
1048
1049                 public override string this [int i] {
1050                         get { return GetAttribute (i); }
1051                 }
1052
1053                 public override string this [string name] {
1054                         get { return GetAttribute (name); }
1055                 }
1056
1057                 public override string this [string name, string ns] {
1058                         get { return GetAttribute (name, ns); }
1059                 }
1060
1061                 public int LineNumber {
1062                         get {
1063                                 IXmlLineInfo info = reader as IXmlLineInfo;
1064                                 return (info != null) ? info.LineNumber : 0;
1065                         }
1066                 }
1067
1068                 public int LinePosition {
1069                         get {
1070                                 IXmlLineInfo info = reader as IXmlLineInfo;
1071                                 return (info != null) ? info.LinePosition : 0;
1072                         }
1073                 }
1074
1075                 public override string LocalName {
1076                         get {
1077                                 if (currentTextValue != null || consumedAttribute)
1078                                         return String.Empty;
1079                                 if (entityReaderStack.Count > 0 && reader.EOF)
1080                                         return ((XmlReader) entityReaderStack.Peek ()).LocalName; // name of EndEntity
1081                                 else if (NodeType == XmlNodeType.Attribute)
1082                                         return (string) attributeLocalNames [currentAttribute];
1083                                 else
1084                                         return reader.LocalName;
1085                         }
1086                 }
1087
1088                 public override string Name {
1089                         get {
1090                                 if (currentTextValue != null || consumedAttribute)
1091                                         return String.Empty;
1092                                 if (entityReaderStack.Count > 0 && reader.EOF)
1093                                         return ((XmlReader) entityReaderStack.Peek ()).Name; // name of EndEntity
1094                                 else if (NodeType == XmlNodeType.Attribute)
1095                                         return currentAttribute;
1096                                 else
1097                                         return reader.Name;
1098                         }
1099                 }
1100
1101                 public override string NamespaceURI {
1102                         get {
1103                                 if (currentTextValue != null || consumedAttribute)
1104                                         return String.Empty;
1105                                 switch (NodeType) {
1106                                 case XmlNodeType.Attribute:
1107                                         return (string) attributeNamespaces [currentAttribute];
1108                                 case XmlNodeType.Element:
1109                                 case XmlNodeType.EndElement:
1110                                         return nsmgr.LookupNamespace (Prefix);
1111                                 default:
1112                                         return String.Empty;
1113                                 }
1114                         }
1115                 }
1116
1117                 public override XmlNameTable NameTable {
1118                         get { return reader.NameTable; }
1119                 }
1120
1121                 public override XmlNodeType NodeType {
1122                         get {
1123                                 if (currentTextValue != null)
1124                                         return isSignificantWhitespace ? XmlNodeType.SignificantWhitespace :
1125                                                 isWhitespace ? XmlNodeType.Whitespace :
1126                                                 XmlNodeType.Text;
1127
1128                                 if (entityReaderStack.Count > 0 && reader.EOF)
1129                                         return XmlNodeType.EndEntity;
1130
1131                                 // If consumedAttribute is true, then entities must be resolved.
1132                                 return consumedAttribute ? XmlNodeType.Text :
1133                                         IsDefault ? XmlNodeType.Attribute :
1134                                         reader.NodeType;
1135                         }
1136                 }
1137
1138                 public XmlParserContext ParserContext {
1139                         get { return XmlSchemaUtil.GetParserContext (reader); }
1140                 }
1141
1142                 public override string Prefix {
1143                         get {
1144                                 if (currentTextValue != null || consumedAttribute)
1145                                         return String.Empty;
1146                                 else if (NodeType == XmlNodeType.Attribute)
1147                                         return (string) attributePrefixes [currentAttribute];
1148                                 else
1149                                         return reader.Prefix;
1150                         }
1151                 }
1152                 
1153                 public override char QuoteChar {
1154                         get {
1155                                 // If it is not actually on an attribute, then it returns
1156                                 // undefined value or '"'.
1157                                 return reader.QuoteChar;
1158                         }
1159                 }
1160
1161                 public override ReadState ReadState {
1162                         get {
1163                                 if (reader.ReadState == ReadState.EndOfFile && currentTextValue != null)
1164                                         return ReadState.Interactive;
1165                                 return reader.ReadState;
1166                         }
1167                 }
1168
1169                 public object SchemaType {
1170                         get {
1171                                 if (currentElement == null)
1172                                         return null;
1173                                 DTDAttListDeclaration decl =
1174                                         DTD.AttListDecls [currentElement];
1175                                 DTDAttributeDefinition def =
1176                                         decl != null ? decl [currentAttribute] : null;
1177                                 return def != null ? def.Datatype : null;
1178                         }
1179                 }
1180
1181                 char [] whitespaceChars = new char [] {' '};
1182                 private string FilterNormalization (string attrName, string rawValue)
1183                 {
1184                         if (DTD == null || NodeType != XmlNodeType.Attribute ||
1185                                 sourceTextReader == null ||
1186                                 !sourceTextReader.Normalization)
1187                                 return rawValue;
1188
1189                         DTDAttributeDefinition def = 
1190                                 dtd.AttListDecls [currentElement].Get (attrName);
1191                         valueBuilder.Append (rawValue);
1192                         valueBuilder.Replace ('\r', ' ');
1193                         valueBuilder.Replace ('\n', ' ');
1194                         valueBuilder.Replace ('\t', ' ');
1195                         try {
1196                                 if (def.Datatype.TokenizedType == XmlTokenizedType.CDATA)
1197                                         return valueBuilder.ToString ();
1198                                 
1199                                 for (int i=0; i < valueBuilder.Length; i++) {
1200                                         if (valueBuilder [i] != ' ')
1201                                                 continue;
1202                                         while (++i < valueBuilder.Length && valueBuilder [i] == ' ')
1203                                                 valueBuilder.Remove (i, 1);
1204                                 }
1205                                 return valueBuilder.ToString ().Trim (whitespaceChars);
1206                         } finally {
1207                                 valueBuilder.Length = 0;
1208                         }
1209                 }
1210
1211                 public override string Value {
1212                         get {
1213                                 if (currentTextValue != null)
1214                                         return currentTextValue;
1215                                 // As to this property, MS.NET seems ignorant of EntityHandling...
1216                                 else if (NodeType == XmlNodeType.Attribute
1217                                         // It also covers default attribute text.
1218                                         || consumedAttribute)
1219                                         return FilterNormalization (Name, (string) attributeValues [currentAttribute]);
1220                                 else
1221                                         return FilterNormalization (Name, reader.Value);
1222                         }
1223                 }
1224
1225                 public override string XmlLang {
1226                         get {
1227                                 string val = this ["xml:lang"];
1228                                 return val != null ? val : reader.XmlLang;
1229                         }
1230                 }
1231
1232                 public XmlResolver XmlResolver {
1233                         set {
1234                                 resolver = value;
1235                         }
1236                 }
1237
1238                 public override XmlSpace XmlSpace {
1239                         get {
1240                                 string val = this ["xml:space"];
1241                                 switch (val) {
1242                                 case "preserve":
1243                                         return XmlSpace.Preserve;
1244                                 case "default":
1245                                         return XmlSpace.Default;
1246                                 default:
1247                                         return reader.XmlSpace;
1248                                 }
1249                         }
1250                 }
1251
1252         }
1253 }