2004-01-13 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / System.Xml / DTDObjectModel.cs
1 //
2 // Mono.Xml.DTDObjectModel
3 //
4 // Author:
5 //      Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
6 //
7 //      (C)2003 Atsushi Enomoto
8 //
9 using System;
10 using System.Collections;
11 using System.Collections.Specialized;
12 using System.Globalization;
13 using System.IO;
14 using System.Text;
15 using System.Xml;
16 using System.Xml.Schema;
17 using Mono.Xml.Schema;
18 using Mono.Xml.Native;
19
20 namespace Mono.Xml
21 {
22         public class DTDObjectModel
23         {
24                 DTDAutomataFactory factory;
25                 DTDElementAutomata rootAutomata;
26                 DTDEmptyAutomata emptyAutomata;
27                 DTDAnyAutomata anyAutomata;
28                 DTDInvalidAutomata invalidAutomata;
29
30                 DTDElementDeclarationCollection elementDecls;
31                 DTDAttListDeclarationCollection attListDecls;
32                 DTDParameterEntityDeclarationCollection peDecls;
33                 DTDEntityDeclarationCollection entityDecls;
34                 DTDNotationDeclarationCollection notationDecls;
35                 ArrayList validationErrors;
36                 XmlResolver resolver;
37                 XmlNameTable nameTable;
38
39                 string baseURI;
40                 string name;
41                 string publicId;
42                 string systemId;
43                 string intSubset;
44                 bool intSubsetHasPERef;
45                 bool isStandalone;
46                 int lineNumber;
47                 int linePosition;
48
49                 public DTDObjectModel (XmlNameTable nameTable)
50                 {
51                         this.nameTable = nameTable;
52                         elementDecls = new DTDElementDeclarationCollection (this);
53                         attListDecls = new DTDAttListDeclarationCollection (this);
54                         entityDecls = new DTDEntityDeclarationCollection (this);
55                         peDecls = new DTDParameterEntityDeclarationCollection (this);
56                         notationDecls = new DTDNotationDeclarationCollection (this);
57                         factory = new DTDAutomataFactory (this);
58                         validationErrors = new ArrayList ();
59                 }
60
61                 public string BaseURI {
62                         get { return baseURI; }
63                         set { baseURI = value; }
64                 }
65
66                 public bool IsStandalone {
67                         get { return isStandalone; }
68                         set { isStandalone = value; }
69                 }
70
71                 public string Name {
72                         get { return name; }
73                         set { name = value; }
74                 }
75
76                 public XmlNameTable NameTable {
77                         get { return nameTable; }
78                 }
79                 
80                 public string PublicId {
81                         get { return publicId; }
82                         set { publicId = value; }
83                 }
84
85                 public string SystemId {
86                         get { return systemId; }
87                         set { systemId = value; }
88                 }
89                 
90                 public string InternalSubset {
91                         get { return intSubset; }
92                         set { intSubset = value; }
93                 }
94
95                 public bool InternalSubsetHasPEReference {
96                         get { return intSubsetHasPERef; }
97                         set { intSubsetHasPERef = value; }
98                 }
99
100                 public int LineNumber {
101                         get { return lineNumber; }
102                         set { lineNumber = value; }
103                 }
104
105                 public int LinePosition {
106                         get { return linePosition; }
107                         set { linePosition = value; }
108                 }
109                 
110                 public string ResolveEntity (string name)
111                 {
112                         DTDEntityDeclaration decl = EntityDecls [name] 
113                                 as DTDEntityDeclaration;
114                         if (decl == null) {
115                                 AddError (new XmlSchemaException ("Required entity was not found.",
116                                         this.LineNumber, this.LinePosition, null, this.BaseURI, null));
117                                 return " ";
118                         }
119                         else
120                                 return decl.EntityValue;
121                 }
122
123                 internal XmlResolver Resolver {
124                         get { return resolver; }
125                 }
126
127                 public XmlResolver XmlResolver {
128                         set { resolver = value; }
129                 }
130
131                 public DTDAutomataFactory Factory {
132                         get { return factory; }
133                 }
134
135                 public DTDElementDeclaration RootElement {
136                         get { return ElementDecls [Name]; }
137                 }
138
139                 public DTDElementDeclarationCollection ElementDecls {
140                         get { return elementDecls; }
141                 }
142
143                 public DTDAttListDeclarationCollection AttListDecls {
144                         get { return attListDecls; }
145                 }
146
147                 public DTDEntityDeclarationCollection EntityDecls {
148                         get { return entityDecls; }
149                 }
150
151                 public DTDParameterEntityDeclarationCollection PEDecls {
152                         get { return peDecls; }
153                 }
154
155                 public DTDNotationDeclarationCollection NotationDecls {
156                         get { return notationDecls; }
157                 }
158
159                 public DTDAutomata RootAutomata {
160                         get {
161                                 if (rootAutomata == null)
162                                         rootAutomata = new DTDElementAutomata (this, this.Name);
163                                 return rootAutomata;
164                         }
165                 }
166
167                 public DTDEmptyAutomata Empty {
168                         get {
169                                 if (emptyAutomata == null)
170                                         emptyAutomata = new DTDEmptyAutomata (this);
171                                 return emptyAutomata;
172                         }
173                 }
174
175                 public DTDAnyAutomata Any {
176                         get {
177                                 if (anyAutomata == null)
178                                         anyAutomata = new DTDAnyAutomata (this);
179                                 return anyAutomata;
180                         }
181                 }
182
183                 public DTDInvalidAutomata Invalid {
184                         get {
185                                 if (invalidAutomata == null)
186                                         invalidAutomata = new DTDInvalidAutomata (this);
187                                 return invalidAutomata;
188                         }
189                 }
190
191                 public XmlSchemaException [] Errors {
192                         get { return validationErrors.ToArray (typeof (XmlSchemaException)) as XmlSchemaException []; }
193                 }
194
195                 public void AddError (XmlSchemaException ex)
196                 {
197                         validationErrors.Add (ex);
198                 }
199         }
200
201         public class DTDElementDeclarationCollection
202         {
203                 Hashtable elementDecls = new Hashtable ();
204                 DTDObjectModel root;
205
206                 public DTDElementDeclarationCollection (DTDObjectModel root)
207                 {
208                         this.root = root;
209                 }
210
211                 public DTDElementDeclaration this [string name] {
212                         get { return elementDecls [name] as DTDElementDeclaration; }
213                 }
214
215                 public void Add (string name, DTDElementDeclaration decl)
216                 {
217                         if (elementDecls [name] != null) {
218                                 this.root.AddError (new XmlSchemaException (String.Format (
219                                         "Element declaration for {0} was already added.",
220                                         name), null));
221                                 return;
222                         }
223                         decl.SetRoot (root);
224                         elementDecls.Add (name, decl);
225                 }
226
227                 public ICollection Keys {
228                         get { return elementDecls.Keys; }
229                 }
230
231                 public ICollection Values {
232                         get { return elementDecls.Values; }
233                 }
234         }
235
236         public class DTDAttListDeclarationCollection
237         {
238                 Hashtable attListDecls = new Hashtable ();
239                 DTDObjectModel root;
240
241                 public DTDAttListDeclarationCollection (DTDObjectModel root)
242                 {
243                         this.root = root;
244                 }
245
246                 public DTDAttListDeclaration this [string name] {
247                         get { return attListDecls [name] as DTDAttListDeclaration; }
248                 }
249
250                 public void Add (string name, DTDAttListDeclaration decl)
251                 {
252                         DTDAttListDeclaration existing = this [name];
253                         if (existing != null) {
254                                 // It should be valid and 
255                                 // has effect of additive declaration.
256                                 foreach (DTDAttributeDefinition def in decl.Definitions)
257                                         if (decl.Get (def.Name) == null)
258                                                 existing.Add (def);
259                         } else {
260                                 decl.SetRoot (root);
261                                 attListDecls.Add (name, decl);
262                         }
263                 }
264
265                 public ICollection Keys {
266                         get { return attListDecls.Keys; }
267                 }
268
269                 public ICollection Values {
270                         get { return attListDecls.Values; }
271                 }
272         }
273
274         public class DTDEntityDeclarationCollection
275         {
276                 Hashtable entityDecls = new Hashtable ();
277                 DTDObjectModel root;
278
279                 public DTDEntityDeclarationCollection (DTDObjectModel root)
280                 {
281                         this.root = root;
282                 }
283
284                 public DTDEntityDeclaration this [string name] {
285                         get { return entityDecls [name] as DTDEntityDeclaration; }
286                 }
287
288                 public void Add (string name, DTDEntityDeclaration decl)
289                 {
290                         if (entityDecls [name] != null)
291                                 throw new InvalidOperationException (String.Format (
292                                         "Entity declaration for {0} was already added.",
293                                         name));
294                         decl.SetRoot (root);
295                         entityDecls.Add (name, decl);
296                 }
297
298                 public ICollection Keys {
299                         get { return entityDecls.Keys; }
300                 }
301
302                 public ICollection Values {
303                         get { return entityDecls.Values; }
304                 }
305         }
306
307         public class DTDNotationDeclarationCollection
308         {
309                 Hashtable notationDecls = new Hashtable ();
310                 DTDObjectModel root;
311
312                 public DTDNotationDeclarationCollection (DTDObjectModel root)
313                 {
314                         this.root = root;
315                 }
316
317                 public DTDNotationDeclaration this [string name] {
318                         get { return notationDecls [name] as DTDNotationDeclaration; }
319                 }
320
321                 public void Add (string name, DTDNotationDeclaration decl)
322                 {
323                         if (notationDecls [name] != null)
324                                 throw new InvalidOperationException (String.Format (
325                                         "Notation declaration for {0} was already added.",
326                                         name));
327                         decl.SetRoot (root);
328                         notationDecls.Add (name, decl);
329                 }
330
331                 public ICollection Keys {
332                         get { return notationDecls.Keys; }
333                 }
334
335                 public ICollection Values {
336                         get { return notationDecls.Values; }
337                 }
338         }
339
340         // This class contains either ElementName or ChildModels.
341         public class DTDContentModel : DTDNode
342         {
343                 DTDObjectModel root;
344                 DTDAutomata compiledAutomata;
345
346                 string ownerElementName;
347                 string elementName;
348                 DTDContentOrderType orderType = DTDContentOrderType.None;
349                 DTDContentModelCollection childModels = new DTDContentModelCollection ();
350                 DTDOccurence occurence = DTDOccurence.One;
351
352                 internal DTDContentModel (DTDObjectModel root, string ownerElementName)
353                 {
354                         this.root = root;
355                         this.ownerElementName = ownerElementName;
356                 }
357
358                 public DTDContentModelCollection ChildModels {
359                         get { return childModels; }
360                         set { childModels = value; }
361                 }
362
363                 public DTDElementDeclaration ElementDecl {
364                         get { return root.ElementDecls [ownerElementName]; }
365                 }
366
367                 public string ElementName {
368                         get { return elementName; }
369                         set { elementName = value; }
370                 }
371
372                 public DTDOccurence Occurence {
373                         get { return occurence; }
374                         set { occurence = value; }
375                 }
376
377                 public DTDContentOrderType OrderType {
378                         get { return orderType; }
379                         set { orderType = value; }
380                 }
381
382                 public DTDAutomata GetAutomata ()
383                 {
384                         if (compiledAutomata == null)
385                                 Compile ();
386                         return compiledAutomata;
387                 }
388
389                 public DTDAutomata Compile ()
390                 {
391                         compiledAutomata = CompileInternal ();
392                         return compiledAutomata;
393                 }
394
395                 private DTDAutomata CompileInternal ()
396                 {
397                         if (ElementDecl.IsAny)
398                                 return root.Any;
399                         if (ElementDecl.IsEmpty)
400                                 return root.Empty;
401
402                         DTDAutomata basis = GetBasicContentAutomata ();
403                         switch (Occurence) {
404                         case DTDOccurence.One:
405                                 return basis;
406                         case DTDOccurence.Optional:
407                                 return Choice (root.Empty, basis);
408                         case DTDOccurence.OneOrMore:
409                                 return new DTDOneOrMoreAutomata (root, basis);
410                         case DTDOccurence.ZeroOrMore:
411                                 return Choice (root.Empty, new DTDOneOrMoreAutomata (root, basis));
412                         }
413                         throw new InvalidOperationException ();
414                 }
415
416                 private DTDAutomata GetBasicContentAutomata ()
417                 {
418                         if (ElementName != null)
419                                 return new DTDElementAutomata (root, ElementName);
420                         switch (ChildModels.Count) {
421                         case 0:
422                                 return root.Empty;
423                         case 1:
424                                 return ChildModels [0].GetAutomata ();
425                         }
426
427                         DTDAutomata current = null;
428                         int childCount = ChildModels.Count;
429                         switch (OrderType) {
430                         case DTDContentOrderType.Seq:
431                                 current = Sequence (
432                                         ChildModels [childCount - 2].GetAutomata (),
433                                         ChildModels [childCount - 1].GetAutomata ());
434                                 for (int i = childCount - 2; i > 0; i--)
435                                         current = Sequence (
436                                                 ChildModels [i - 1].GetAutomata (), current);
437                                 return current;
438                         case DTDContentOrderType.Or:
439                                 current = Choice (
440                                         ChildModels [childCount - 2].GetAutomata (),
441                                         ChildModels [childCount - 1].GetAutomata ());
442                                 for (int i = childCount - 2; i > 0; i--)
443                                         current = Choice (
444                                                 ChildModels [i - 1].GetAutomata (), current);
445                                 return current;
446                         default:
447                                 throw new InvalidOperationException ("Invalid pattern specification");
448                         }
449                 }
450
451                 private DTDAutomata Sequence (DTDAutomata l, DTDAutomata r)
452                 {
453                         return root.Factory.Sequence (l, r);
454                 }
455
456                 private DTDAutomata Choice (DTDAutomata l, DTDAutomata r)
457                 {
458                         return l.MakeChoice (r);
459                 }
460
461         }
462
463         public class DTDContentModelCollection
464         {
465                 ArrayList contentModel = new ArrayList ();
466
467                 public DTDContentModelCollection ()
468                 {
469                 }
470
471                 public DTDContentModel this [int i] {
472                         get { return contentModel [i] as DTDContentModel; }
473                 }
474
475                 public int Count {
476                         get { return contentModel.Count; }
477                 }
478
479                 public void Add (DTDContentModel model)
480                 {
481                         contentModel.Add (model);
482                 }
483         }
484
485         public abstract class DTDNode : IXmlLineInfo
486         {
487                 DTDObjectModel root;
488                 bool isInternalSubset;
489                 string baseURI;
490                 int lineNumber;
491                 int linePosition;
492
493                 public string BaseURI {
494                         get { return baseURI; }
495                         set { baseURI = value; }
496                 }
497
498                 public bool IsInternalSubset {
499                         get { return isInternalSubset; }
500                         set { isInternalSubset = value; }
501                 }
502
503                 public int LineNumber {
504                         get { return lineNumber; }
505                         set { lineNumber = value; }
506                 }
507
508                 public int LinePosition {
509                         get { return linePosition; }
510                         set { linePosition = value; }
511                 }
512
513                 public bool HasLineInfo ()
514                 {
515                         return lineNumber != 0;
516                 }
517
518                 internal void SetRoot (DTDObjectModel root)
519                 {
520                         this.root = root;
521                         if (BaseURI == null)
522                                 this.BaseURI = root.BaseURI;
523                 }
524
525                 protected DTDObjectModel Root {
526                         get { return root; }
527                 }
528         }
529
530         public class DTDElementDeclaration : DTDNode
531         {
532                 DTDObjectModel root;
533                 DTDContentModel contentModel;
534                 string name;
535                 bool isEmpty;
536                 bool isAny;
537                 bool isMixedContent;
538
539                 internal DTDElementDeclaration (DTDObjectModel root)
540                 {
541                         this.root = root;
542                 }
543
544                 public string Name {
545                         get { return name; }
546                         set { name = value; }
547                 }
548                 public bool IsEmpty {
549                         get { return isEmpty; }
550                         set { isEmpty = value; }
551                 }
552
553                 public bool IsAny {
554                         get { return isAny; }
555                         set { isAny = value; }
556                 }
557
558                 public bool IsMixedContent {
559                         get { return isMixedContent; }
560                         set { isMixedContent = value; }
561                 }
562
563                 public DTDContentModel ContentModel {
564                         get {
565                                 if (contentModel == null)
566                                         contentModel = new DTDContentModel (root, Name);
567                                 return contentModel;
568                         }
569                 }
570
571                 public DTDAttListDeclaration Attributes {
572                         get {
573                                 return Root.AttListDecls [Name];
574                         }
575                 }
576         }
577
578         public class DTDAttributeDefinition : DTDNode
579         {
580                 string name;
581                 XmlSchemaDatatype datatype;
582                 ArrayList enumeratedLiterals = new ArrayList ();
583                 string unresolvedDefault;
584                 ArrayList enumeratedNotations = new ArrayList ();
585                 DTDAttributeOccurenceType occurenceType = DTDAttributeOccurenceType.None;
586                 string resolvedDefaultValue;
587                 string resolvedNormalizedDefaultValue;
588
589                 internal DTDAttributeDefinition (DTDObjectModel root)
590                 {
591                         this.SetRoot (root);
592                 }
593
594                 public string Name {
595                         get { return name; }
596                         set { name =value; }
597                 }
598
599                 public XmlSchemaDatatype Datatype {
600                         get { return datatype; }
601                         set { datatype = value; }
602                 }
603
604                 public DTDAttributeOccurenceType OccurenceType {
605                         get { return this.occurenceType; }
606                         set { this.occurenceType = value; }
607                 }
608
609                 // entity reference inside enumerated values are not allowed,
610                 // but on the other hand, they are allowed inside default value.
611                 // Then I decided to use string ArrayList for enumerated values,
612                 // and unresolved string value for DefaultValue.
613                 public ArrayList EnumeratedAttributeDeclaration {
614                         get { return this.enumeratedLiterals; }
615                 }
616
617                 public ArrayList EnumeratedNotations {
618                         get { return this.enumeratedNotations; }
619                 }
620
621                 public string DefaultValue {
622                         get {
623                                 if (resolvedDefaultValue == null)
624                                         resolvedDefaultValue = ComputeDefaultValue ();
625                                 return resolvedDefaultValue;
626                         }
627                 }
628
629                 public string NormalizedDefaultValue {
630                         get {
631                                 if (resolvedNormalizedDefaultValue == null) {
632                                         object o = Datatype.ParseValue (ComputeDefaultValue (), null, null);
633                                         resolvedNormalizedDefaultValue = 
634                                                 (o is string []) ? 
635                                                 String.Join (" ", (string []) o) :
636                                                 o.ToString ();
637                                 }
638                                 return resolvedNormalizedDefaultValue;
639                         }
640                 }
641
642                 public string UnresolvedDefaultValue {
643                         get { return this.unresolvedDefault; }
644                         set { this.unresolvedDefault = value; }
645                 }
646
647                 public char QuoteChar {
648                         get {
649                                 return UnresolvedDefaultValue.Length > 0 ?
650                                         this.UnresolvedDefaultValue [0] :
651                                         '"';
652                         }
653                 }
654
655                 private string ComputeDefaultValue ()
656                 {
657                         if (UnresolvedDefaultValue == null)
658                                 return null;
659
660                         StringBuilder sb = new StringBuilder ();
661                         int pos = 0;
662                         int next = 0;
663                         string value = this.UnresolvedDefaultValue;
664                         while ((next = value.IndexOf ('&', pos)) >= 0) {
665                                 int semicolon = value.IndexOf (';', next);
666                                 if (value [next + 1] == '#') {
667                                         // character reference.
668                                         char c = value [next + 2];
669                                         NumberStyles style = NumberStyles.Integer;
670                                         string spec;
671                                         if (c == 'x' || c == 'X') {
672                                                 spec = value.Substring (next + 3, semicolon - next - 3);
673                                                 style |= NumberStyles.HexNumber;
674                                         }
675                                         else
676                                                 spec = value.Substring (next + 2, semicolon - next - 2);
677                                         sb.Append ((char) int.Parse (spec, style));
678                                 } else {
679                                         sb.Append (value.Substring (pos, next - 1));
680                                         string name = value.Substring (next + 1, semicolon - 2);
681                                         char predefined = XmlChar.GetPredefinedEntity (name);
682                                         if (predefined != 0)
683                                                 sb.Append (predefined);
684                                         else
685                                                 sb.Append (Root.ResolveEntity (name));
686                                 }
687                                 pos = semicolon + 1;
688                         }
689                         sb.Append (value.Substring (pos));
690                         // strip quote chars
691                         string ret = sb.ToString (1, sb.Length - 2);
692                         sb.Length = 0;
693                         return ret;
694                 }
695
696         }
697
698         public class DTDAttListDeclaration : DTDNode
699         {
700                 string name;
701                 Hashtable attributeOrders = new Hashtable ();
702                 ArrayList attributes = new ArrayList ();
703
704                 internal DTDAttListDeclaration (DTDObjectModel root)
705                 {
706                         SetRoot (root);
707                 }
708
709                 public string Name {
710                         get { return name; }
711                         set { name = value; }
712                 }
713
714                 public DTDAttributeDefinition this [int i] {
715                         get { return Get (i); }
716                 }
717
718                 public DTDAttributeDefinition this [string name] {
719                         get { return Get (name); }
720                 }
721
722                 public DTDAttributeDefinition Get (int i)
723                 {
724                         return attributes [i] as DTDAttributeDefinition;
725                 }
726
727                 public DTDAttributeDefinition Get (string name)
728                 {
729                         object o = attributeOrders [name];
730                         if (o != null)
731                                 return attributes [(int) o] as DTDAttributeDefinition;
732                         else
733                                 return null;
734                 }
735
736                 public ICollection Definitions {
737                         get { return attributes; }
738                 }
739
740                 public void Add (DTDAttributeDefinition def)
741                 {
742                         if (attributeOrders [def.Name] != null)
743                                 throw new InvalidOperationException (String.Format (
744                                         "Attribute definition for {0} was already added at element {1}.",
745                                         def.Name, this.Name));
746                         def.SetRoot (Root);
747                         attributeOrders.Add (def.Name, attributes.Count);
748                         attributes.Add (def);
749                 }
750
751                 public int Count {
752                         get { return attributeOrders.Count; }
753                 }
754         }
755
756         public class DTDEntityBase : DTDNode
757         {
758                 string name;
759                 string publicId;
760                 string systemId;
761                 string literalValue;
762
763                 public string Name {
764                         get { return name; }
765                         set { name = value; }
766                 }
767
768                 public string PublicId {
769                         get { return publicId; }
770                         set { publicId = value; }
771                 }
772
773                 public string SystemId {
774                         get { return systemId; }
775                         set { systemId = value; }
776                 }
777
778                 public string LiteralEntityValue {
779                         get { return literalValue; }
780                         set { literalValue = value; }
781                 }
782
783         }
784
785         public class DTDEntityDeclaration : DTDEntityBase
786         {
787                 string entityValue;
788                 string notationName;
789
790                 StringCollection ReferencingEntities = new StringCollection ();
791
792                 bool scanned;
793                 bool recursed;
794                 bool hasExternalReference;
795
796                 internal DTDEntityDeclaration (DTDObjectModel root)
797                 {
798                         this.SetRoot (root);
799                 }
800
801                 public string NotationName {
802                         get { return notationName; }
803                         set { notationName = value; }
804                 }
805
806                 public bool HasExternalReference {
807                         get {
808                                 if (!scanned)
809                                         ScanEntityValue (new StringCollection ());
810                                 return hasExternalReference;
811                         }
812                 }
813
814                 public string EntityValue {
815                         get {
816                                 if (PublicId == null && SystemId == null && LiteralEntityValue == null)
817                                         return String.Empty;
818
819                                 if (entityValue == null) {
820                                         if (NotationName != null)
821                                                 entityValue = "";
822                                         else if (SystemId == null || SystemId == String.Empty) {
823                                                 // FIXME: Isn't it an error??
824                                                 entityValue = LiteralEntityValue;
825                                                 if (entityValue == null)
826                                                         entityValue = String.Empty;
827                                         } else {
828                                                 entityValue = ResolveExternalEntity (Root.Resolver);
829                                         }
830                                         // Check illegal recursion.
831                                         ScanEntityValue (new StringCollection ());
832                                 }
833                                 return entityValue;
834                         }
835                 }
836
837                 // It returns whether the entity contains references to external entities.
838                 public void ScanEntityValue (StringCollection refs)
839                 {
840                         // To modify this code, beware nesting between this and EntityValue.
841                         string value = EntityValue;
842                         if (this.SystemId != null)
843                                 hasExternalReference = true;
844
845                         if (recursed)
846                                 throw new XmlException ("Entity recursion was found.");
847                         recursed = true;
848
849                         if (scanned) {
850                                 foreach (string referenced in refs)
851                                         if (this.ReferencingEntities.Contains (referenced))
852                                                 throw new XmlException (String.Format (
853                                                         "Nested entity was found between {0} and {1}",
854                                                         referenced, Name));
855                                 recursed = false;
856                                 return;
857                         }
858
859                         int len = value.Length;
860                         int start = 0;
861                         for (int i=0; i<len; i++) {
862                                 switch (value [i]) {
863                                 case '&':
864                                         start = i+1;
865                                         break;
866                                 case ';':
867                                         if (start == 0)
868                                                 break;
869                                         string name = value.Substring (start, i - start);
870                                         if (name.Length == 0)
871                                                 throw new XmlException (this as IXmlLineInfo, "Entity reference name is missing.");
872                                         if (name [0] == '#')
873                                                 break;  // character reference
874                                         // FIXME: Should be checked, but how to handle entity for ENTITY attribute?
875 //                                      if (!XmlChar.IsName (name))
876 //                                              throw new XmlException (this as IXmlLineInfo, "Invalid entity reference name.");
877                                         if (XmlChar.GetPredefinedEntity (name) != 0)
878                                                 break;  // predefined reference
879
880                                         this.ReferencingEntities.Add (name);
881                                         DTDEntityDeclaration decl = Root.EntityDecls [name];
882                                         if (decl != null) {
883                                                 if (decl.SystemId != null)
884                                                         hasExternalReference = true;
885                                                 refs.Add (Name);
886                                                 decl.ScanEntityValue (refs);
887                                                 foreach (string str in decl.ReferencingEntities)
888                                                         ReferencingEntities.Add (str);
889                                                 refs.Remove (Name);
890                                                 value = value.Remove (start - 1, name.Length + 2);
891                                                 value = value.Insert (start - 1, decl.EntityValue);
892                                                 i -= name.Length + 1; // not +2, because of immediate i++ .
893                                                 len = value.Length;
894                                         }
895                                         start = 0;
896                                         break;
897                                 }
898                         }
899                         if (start != 0)
900                                 Root.AddError (new XmlSchemaException ("Invalid reference character '&' is specified.",
901                                         this.LineNumber, this.LinePosition, null, this.BaseURI, null));
902                         scanned = true;
903                         entityValue = value;
904                         recursed = false;
905                 }
906
907                 private string ResolveExternalEntity (XmlResolver resolver)
908                 {
909                         if (resolver == null)
910                                 return String.Empty;
911
912                         string baseUri = Root.BaseURI;
913                         if (baseUri == "")
914                                 baseUri = null;
915                         Uri uri = resolver.ResolveUri (
916                                 baseUri != null ? new Uri (baseUri) : null, SystemId);
917                         Stream stream = null;
918                         try {
919                                 stream = resolver.GetEntity (uri, null, typeof (Stream)) as Stream;
920                         } catch (Exception ex) { // FIXME: (wishlist) bad catch ;-(
921                                 Root.AddError (new XmlSchemaException ("Cannot resolve external entity " + uri.ToString () + " .",
922                                         this.LineNumber, this.LinePosition, null, this.BaseURI, ex));
923                         }
924                         if (stream == null)
925                                 return String.Empty;
926                         XmlTextReader extEntReader = new XmlTextReader (uri.AbsolutePath, stream, this.Root.NameTable);
927                         extEntReader.SkipTextDeclaration ();
928                         TextReader reader = extEntReader.GetRemainder ();
929                         extEntReader.Close ();
930
931                         StringBuilder sb = new StringBuilder ();
932
933                         bool checkTextDecl = true;
934                         while (reader.Peek () != -1) {
935                                 sb.Append ((char) reader.Read ());
936                                 if (checkTextDecl && sb.Length == 6) {
937                                         if (sb.ToString () == "<?xml ") {
938                                                 // Skip Text declaration.
939                                                 sb.Length = 0;
940                                                 StringBuilder textdecl = new StringBuilder ();
941                                                 while (reader.Peek () != '>' && reader.Peek () != -1)
942                                                         textdecl.Append ((char) reader.Read ());
943                                                 if (textdecl.ToString ().IndexOf ("encoding") < 0)
944                                                         throw new XmlException ("Text declaration must have encoding specification: " + BaseURI);
945                                                 if (textdecl.ToString ().IndexOf ("standalone") >= 0)
946                                                         throw new XmlException ("Text declaration cannot have standalone declaration: " + BaseURI);
947                                         }
948                                         checkTextDecl = false;
949                                 }
950                         }
951                         return sb.ToString ();
952                 }
953         }
954
955         public class DTDNotationDeclaration : DTDNode
956         {
957                 string name;
958                 string localName;
959                 string prefix;
960                 string publicId;
961                 string systemId;
962
963                 public string Name {
964                         get { return name; }
965                         set { name = value; }
966                 }
967
968                 public string PublicId {
969                         get { return publicId; }
970                         set { publicId = value; }
971                 }
972
973                 public string SystemId {
974                         get { return systemId; }
975                         set { systemId = value; }
976                 }
977
978                 public string LocalName {
979                         get { return localName; }
980                         set { localName = value; }
981                 }
982
983                 public string Prefix {
984                         get { return prefix; }
985                         set { prefix = value; }
986                 }
987
988                 internal DTDNotationDeclaration (DTDObjectModel root)
989                 {
990                         SetRoot (root);
991                 }
992         }
993
994         public class DTDParameterEntityDeclarationCollection
995         {
996                 Hashtable peDecls = new Hashtable ();
997                 DTDObjectModel root;
998
999                 public DTDParameterEntityDeclarationCollection (DTDObjectModel root)
1000                 {
1001                         this.root = root;
1002                 }
1003
1004                 public DTDParameterEntityDeclaration this [string name] {
1005                         get { return peDecls [name] as DTDParameterEntityDeclaration; }
1006                 }
1007
1008                 public void Add (string name, DTDParameterEntityDeclaration decl)
1009                 {
1010                         if (peDecls [name] != null) {
1011 //                              this.root.AddError (new XmlSchemaException (String.Format (
1012 //                                      "Parameter entity declaration for {0} was already added.",
1013 //                                      name), null));
1014                                 return;
1015                         }
1016                         decl.SetRoot (root);
1017                         peDecls.Add (name, decl);
1018                 }
1019
1020                 public ICollection Keys {
1021                         get { return peDecls.Keys; }
1022                 }
1023
1024                 public ICollection Values {
1025                         get { return peDecls.Values; }
1026                 }
1027         }
1028
1029         public class DTDParameterEntityDeclaration : DTDEntityBase
1030         {
1031                 string resolvedValue;
1032                 Exception loadException;
1033                 bool loadFailed;
1034
1035                 public bool LoadFailed {
1036                         get { return loadFailed; }
1037                         set { loadFailed = value; }
1038                 }
1039
1040                 public string Value {
1041                         get {
1042                                 if (LiteralEntityValue != null)
1043                                         return LiteralEntityValue;
1044                                 if (resolvedValue == null)
1045                                         throw new InvalidOperationException ();
1046                                 return resolvedValue;
1047                         }
1048                 }
1049
1050                 public void Resolve (XmlResolver resolver)
1051                 {
1052                         if (resolver == null) {
1053                                 resolvedValue = String.Empty;
1054                                 LoadFailed = true;
1055                                 return;
1056                         }
1057
1058                         Uri baseUri = null;
1059                         try {
1060                                 if (BaseURI != null && BaseURI.Length > 0)
1061                                         baseUri = new Uri (BaseURI);
1062                         } catch (UriFormatException) {
1063                         }
1064
1065                         Uri absUri = resolver.ResolveUri (baseUri, SystemId);
1066                         string absPath = absUri.ToString ();
1067
1068                         try {
1069                                 Stream s = resolver.GetEntity (absUri, null, typeof (Stream)) as Stream;
1070                                 XmlTextReader xtr = new XmlTextReader (s);
1071                                 xtr.SkipTextDeclaration ();
1072                                 resolvedValue = xtr.GetRemainder ().ReadToEnd ();
1073                         } catch (IOException ex) {
1074                                 loadException = ex;
1075                                 resolvedValue = String.Empty;
1076                                 LoadFailed = true;
1077                         }
1078                 }
1079         }
1080
1081         public enum DTDContentOrderType
1082         {
1083                 None,
1084                 Seq,
1085                 Or
1086         }
1087
1088         public enum DTDAttributeOccurenceType
1089         {
1090                 None,
1091                 Required,
1092                 Optional,
1093                 Fixed
1094         }
1095
1096         public enum DTDOccurence
1097         {
1098                 One,
1099                 Optional,
1100                 ZeroOrMore,
1101                 OneOrMore
1102         }
1103 }