2 // Mono.Xml.DTDObjectModel
5 // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
7 // (C)2003 Atsushi Enomoto
10 using System.Collections;
11 using System.Collections.Specialized;
12 using System.Globalization;
16 using System.Xml.Schema;
17 using Mono.Xml.Schema;
18 using Mono.Xml.Native;
22 public class DTDObjectModel
24 DTDAutomataFactory factory;
25 DTDElementAutomata rootAutomata;
26 DTDEmptyAutomata emptyAutomata;
27 DTDAnyAutomata anyAutomata;
28 DTDInvalidAutomata invalidAutomata;
30 DTDElementDeclarationCollection elementDecls;
31 DTDAttListDeclarationCollection attListDecls;
32 DTDParameterEntityDeclarationCollection peDecls;
33 DTDEntityDeclarationCollection entityDecls;
34 DTDNotationDeclarationCollection notationDecls;
35 ArrayList validationErrors;
37 XmlNameTable nameTable;
44 bool intSubsetHasPERef;
49 public DTDObjectModel (XmlNameTable nameTable)
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 ();
61 public string BaseURI {
62 get { return baseURI; }
63 set { baseURI = value; }
66 public bool IsStandalone {
67 get { return isStandalone; }
68 set { isStandalone = value; }
76 public XmlNameTable NameTable {
77 get { return nameTable; }
80 public string PublicId {
81 get { return publicId; }
82 set { publicId = value; }
85 public string SystemId {
86 get { return systemId; }
87 set { systemId = value; }
90 public string InternalSubset {
91 get { return intSubset; }
92 set { intSubset = value; }
95 public bool InternalSubsetHasPEReference {
96 get { return intSubsetHasPERef; }
97 set { intSubsetHasPERef = value; }
100 public int LineNumber {
101 get { return lineNumber; }
102 set { lineNumber = value; }
105 public int LinePosition {
106 get { return linePosition; }
107 set { linePosition = value; }
110 public string ResolveEntity (string name)
112 DTDEntityDeclaration decl = EntityDecls [name]
113 as DTDEntityDeclaration;
115 AddError (new XmlSchemaException ("Required entity was not found.",
116 this.LineNumber, this.LinePosition, null, this.BaseURI, null));
120 return decl.EntityValue;
123 internal XmlResolver Resolver {
124 get { return resolver; }
127 public XmlResolver XmlResolver {
128 set { resolver = value; }
131 public DTDAutomataFactory Factory {
132 get { return factory; }
135 public DTDElementDeclaration RootElement {
136 get { return ElementDecls [Name]; }
139 public DTDElementDeclarationCollection ElementDecls {
140 get { return elementDecls; }
143 public DTDAttListDeclarationCollection AttListDecls {
144 get { return attListDecls; }
147 public DTDEntityDeclarationCollection EntityDecls {
148 get { return entityDecls; }
151 public DTDParameterEntityDeclarationCollection PEDecls {
152 get { return peDecls; }
155 public DTDNotationDeclarationCollection NotationDecls {
156 get { return notationDecls; }
159 public DTDAutomata RootAutomata {
161 if (rootAutomata == null)
162 rootAutomata = new DTDElementAutomata (this, this.Name);
167 public DTDEmptyAutomata Empty {
169 if (emptyAutomata == null)
170 emptyAutomata = new DTDEmptyAutomata (this);
171 return emptyAutomata;
175 public DTDAnyAutomata Any {
177 if (anyAutomata == null)
178 anyAutomata = new DTDAnyAutomata (this);
183 public DTDInvalidAutomata Invalid {
185 if (invalidAutomata == null)
186 invalidAutomata = new DTDInvalidAutomata (this);
187 return invalidAutomata;
191 public XmlSchemaException [] Errors {
192 get { return validationErrors.ToArray (typeof (XmlSchemaException)) as XmlSchemaException []; }
195 public void AddError (XmlSchemaException ex)
197 validationErrors.Add (ex);
201 public class DTDElementDeclarationCollection
203 Hashtable elementDecls = new Hashtable ();
206 public DTDElementDeclarationCollection (DTDObjectModel root)
211 public DTDElementDeclaration this [string name] {
212 get { return elementDecls [name] as DTDElementDeclaration; }
215 public void Add (string name, DTDElementDeclaration decl)
217 if (elementDecls [name] != null) {
218 this.root.AddError (new XmlSchemaException (String.Format (
219 "Element declaration for {0} was already added.",
224 elementDecls.Add (name, decl);
227 public ICollection Keys {
228 get { return elementDecls.Keys; }
231 public ICollection Values {
232 get { return elementDecls.Values; }
236 public class DTDAttListDeclarationCollection
238 Hashtable attListDecls = new Hashtable ();
241 public DTDAttListDeclarationCollection (DTDObjectModel root)
246 public DTDAttListDeclaration this [string name] {
247 get { return attListDecls [name] as DTDAttListDeclaration; }
250 public void Add (string name, DTDAttListDeclaration decl)
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)
261 attListDecls.Add (name, decl);
265 public ICollection Keys {
266 get { return attListDecls.Keys; }
269 public ICollection Values {
270 get { return attListDecls.Values; }
274 public class DTDEntityDeclarationCollection
276 Hashtable entityDecls = new Hashtable ();
279 public DTDEntityDeclarationCollection (DTDObjectModel root)
284 public DTDEntityDeclaration this [string name] {
285 get { return entityDecls [name] as DTDEntityDeclaration; }
288 public void Add (string name, DTDEntityDeclaration decl)
290 if (entityDecls [name] != null)
291 throw new InvalidOperationException (String.Format (
292 "Entity declaration for {0} was already added.",
295 entityDecls.Add (name, decl);
298 public ICollection Keys {
299 get { return entityDecls.Keys; }
302 public ICollection Values {
303 get { return entityDecls.Values; }
307 public class DTDNotationDeclarationCollection
309 Hashtable notationDecls = new Hashtable ();
312 public DTDNotationDeclarationCollection (DTDObjectModel root)
317 public DTDNotationDeclaration this [string name] {
318 get { return notationDecls [name] as DTDNotationDeclaration; }
321 public void Add (string name, DTDNotationDeclaration decl)
323 if (notationDecls [name] != null)
324 throw new InvalidOperationException (String.Format (
325 "Notation declaration for {0} was already added.",
328 notationDecls.Add (name, decl);
331 public ICollection Keys {
332 get { return notationDecls.Keys; }
335 public ICollection Values {
336 get { return notationDecls.Values; }
340 // This class contains either ElementName or ChildModels.
341 public class DTDContentModel : DTDNode
344 DTDAutomata compiledAutomata;
346 string ownerElementName;
348 DTDContentOrderType orderType = DTDContentOrderType.None;
349 DTDContentModelCollection childModels = new DTDContentModelCollection ();
350 DTDOccurence occurence = DTDOccurence.One;
352 internal DTDContentModel (DTDObjectModel root, string ownerElementName)
355 this.ownerElementName = ownerElementName;
358 public DTDContentModelCollection ChildModels {
359 get { return childModels; }
360 set { childModels = value; }
363 public DTDElementDeclaration ElementDecl {
364 get { return root.ElementDecls [ownerElementName]; }
367 public string ElementName {
368 get { return elementName; }
369 set { elementName = value; }
372 public DTDOccurence Occurence {
373 get { return occurence; }
374 set { occurence = value; }
377 public DTDContentOrderType OrderType {
378 get { return orderType; }
379 set { orderType = value; }
382 public DTDAutomata GetAutomata ()
384 if (compiledAutomata == null)
386 return compiledAutomata;
389 public DTDAutomata Compile ()
391 compiledAutomata = CompileInternal ();
392 return compiledAutomata;
395 private DTDAutomata CompileInternal ()
397 if (ElementDecl.IsAny)
399 if (ElementDecl.IsEmpty)
402 DTDAutomata basis = GetBasicContentAutomata ();
404 case DTDOccurence.One:
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));
413 throw new InvalidOperationException ();
416 private DTDAutomata GetBasicContentAutomata ()
418 if (ElementName != null)
419 return new DTDElementAutomata (root, ElementName);
420 switch (ChildModels.Count) {
424 return ChildModels [0].GetAutomata ();
427 DTDAutomata current = null;
428 int childCount = ChildModels.Count;
430 case DTDContentOrderType.Seq:
432 ChildModels [childCount - 2].GetAutomata (),
433 ChildModels [childCount - 1].GetAutomata ());
434 for (int i = childCount - 2; i > 0; i--)
436 ChildModels [i - 1].GetAutomata (), current);
438 case DTDContentOrderType.Or:
440 ChildModels [childCount - 2].GetAutomata (),
441 ChildModels [childCount - 1].GetAutomata ());
442 for (int i = childCount - 2; i > 0; i--)
444 ChildModels [i - 1].GetAutomata (), current);
447 throw new InvalidOperationException ("Invalid pattern specification");
451 private DTDAutomata Sequence (DTDAutomata l, DTDAutomata r)
453 return root.Factory.Sequence (l, r);
456 private DTDAutomata Choice (DTDAutomata l, DTDAutomata r)
458 return l.MakeChoice (r);
463 public class DTDContentModelCollection
465 ArrayList contentModel = new ArrayList ();
467 public DTDContentModelCollection ()
471 public DTDContentModel this [int i] {
472 get { return contentModel [i] as DTDContentModel; }
476 get { return contentModel.Count; }
479 public void Add (DTDContentModel model)
481 contentModel.Add (model);
485 public abstract class DTDNode : IXmlLineInfo
488 bool isInternalSubset;
493 public string BaseURI {
494 get { return baseURI; }
495 set { baseURI = value; }
498 public bool IsInternalSubset {
499 get { return isInternalSubset; }
500 set { isInternalSubset = value; }
503 public int LineNumber {
504 get { return lineNumber; }
505 set { lineNumber = value; }
508 public int LinePosition {
509 get { return linePosition; }
510 set { linePosition = value; }
513 public bool HasLineInfo ()
515 return lineNumber != 0;
518 internal void SetRoot (DTDObjectModel root)
522 this.BaseURI = root.BaseURI;
525 protected DTDObjectModel Root {
530 public class DTDElementDeclaration : DTDNode
533 DTDContentModel contentModel;
539 internal DTDElementDeclaration (DTDObjectModel root)
546 set { name = value; }
548 public bool IsEmpty {
549 get { return isEmpty; }
550 set { isEmpty = value; }
554 get { return isAny; }
555 set { isAny = value; }
558 public bool IsMixedContent {
559 get { return isMixedContent; }
560 set { isMixedContent = value; }
563 public DTDContentModel ContentModel {
565 if (contentModel == null)
566 contentModel = new DTDContentModel (root, Name);
571 public DTDAttListDeclaration Attributes {
573 return Root.AttListDecls [Name];
578 public class DTDAttributeDefinition : DTDNode
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;
589 internal DTDAttributeDefinition (DTDObjectModel root)
599 public XmlSchemaDatatype Datatype {
600 get { return datatype; }
601 set { datatype = value; }
604 public DTDAttributeOccurenceType OccurenceType {
605 get { return this.occurenceType; }
606 set { this.occurenceType = value; }
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; }
617 public ArrayList EnumeratedNotations {
618 get { return this.enumeratedNotations; }
621 public string DefaultValue {
623 if (resolvedDefaultValue == null)
624 resolvedDefaultValue = ComputeDefaultValue ();
625 return resolvedDefaultValue;
629 public string NormalizedDefaultValue {
631 if (resolvedNormalizedDefaultValue == null) {
632 object o = Datatype.ParseValue (ComputeDefaultValue (), null, null);
633 resolvedNormalizedDefaultValue =
635 String.Join (" ", (string []) o) :
638 return resolvedNormalizedDefaultValue;
642 public string UnresolvedDefaultValue {
643 get { return this.unresolvedDefault; }
644 set { this.unresolvedDefault = value; }
647 public char QuoteChar {
649 return UnresolvedDefaultValue.Length > 0 ?
650 this.UnresolvedDefaultValue [0] :
655 private string ComputeDefaultValue ()
657 if (UnresolvedDefaultValue == null)
660 StringBuilder sb = new StringBuilder ();
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;
671 if (c == 'x' || c == 'X') {
672 spec = value.Substring (next + 3, semicolon - next - 3);
673 style |= NumberStyles.HexNumber;
676 spec = value.Substring (next + 2, semicolon - next - 2);
677 sb.Append ((char) int.Parse (spec, style));
679 sb.Append (value.Substring (pos, next - 1));
680 string name = value.Substring (next + 1, semicolon - 2);
681 char predefined = XmlChar.GetPredefinedEntity (name);
683 sb.Append (predefined);
685 sb.Append (Root.ResolveEntity (name));
689 sb.Append (value.Substring (pos));
691 string ret = sb.ToString (1, sb.Length - 2);
698 public class DTDAttListDeclaration : DTDNode
701 Hashtable attributeOrders = new Hashtable ();
702 ArrayList attributes = new ArrayList ();
704 internal DTDAttListDeclaration (DTDObjectModel root)
711 set { name = value; }
714 public DTDAttributeDefinition this [int i] {
715 get { return Get (i); }
718 public DTDAttributeDefinition this [string name] {
719 get { return Get (name); }
722 public DTDAttributeDefinition Get (int i)
724 return attributes [i] as DTDAttributeDefinition;
727 public DTDAttributeDefinition Get (string name)
729 object o = attributeOrders [name];
731 return attributes [(int) o] as DTDAttributeDefinition;
736 public ICollection Definitions {
737 get { return attributes; }
740 public void Add (DTDAttributeDefinition def)
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));
747 attributeOrders.Add (def.Name, attributes.Count);
748 attributes.Add (def);
752 get { return attributeOrders.Count; }
756 public class DTDEntityBase : DTDNode
765 set { name = value; }
768 public string PublicId {
769 get { return publicId; }
770 set { publicId = value; }
773 public string SystemId {
774 get { return systemId; }
775 set { systemId = value; }
778 public string LiteralEntityValue {
779 get { return literalValue; }
780 set { literalValue = value; }
785 public class DTDEntityDeclaration : DTDEntityBase
790 StringCollection ReferencingEntities = new StringCollection ();
794 bool hasExternalReference;
796 internal DTDEntityDeclaration (DTDObjectModel root)
801 public string NotationName {
802 get { return notationName; }
803 set { notationName = value; }
806 public bool HasExternalReference {
809 ScanEntityValue (new StringCollection ());
810 return hasExternalReference;
814 public string EntityValue {
816 if (PublicId == null && SystemId == null && LiteralEntityValue == null)
819 if (entityValue == null) {
820 if (NotationName != null)
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;
828 entityValue = ResolveExternalEntity (Root.Resolver);
830 // Check illegal recursion.
831 ScanEntityValue (new StringCollection ());
837 // It returns whether the entity contains references to external entities.
838 public void ScanEntityValue (StringCollection refs)
840 // To modify this code, beware nesting between this and EntityValue.
841 string value = EntityValue;
842 if (this.SystemId != null)
843 hasExternalReference = true;
846 throw new XmlException ("Entity recursion was found.");
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}",
859 int len = value.Length;
861 for (int i=0; i<len; i++) {
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.");
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
880 this.ReferencingEntities.Add (name);
881 DTDEntityDeclaration decl = Root.EntityDecls [name];
883 if (decl.SystemId != null)
884 hasExternalReference = true;
886 decl.ScanEntityValue (refs);
887 foreach (string str in decl.ReferencingEntities)
888 ReferencingEntities.Add (str);
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++ .
900 Root.AddError (new XmlSchemaException ("Invalid reference character '&' is specified.",
901 this.LineNumber, this.LinePosition, null, this.BaseURI, null));
907 private string ResolveExternalEntity (XmlResolver resolver)
909 if (resolver == null)
912 string baseUri = Root.BaseURI;
915 Uri uri = resolver.ResolveUri (
916 baseUri != null ? new Uri (baseUri) : null, SystemId);
917 Stream stream = null;
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));
926 XmlTextReader extEntReader = new XmlTextReader (uri.AbsolutePath, stream, this.Root.NameTable);
927 extEntReader.SkipTextDeclaration ();
928 TextReader reader = extEntReader.GetRemainder ();
929 extEntReader.Close ();
931 StringBuilder sb = new StringBuilder ();
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.
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);
948 checkTextDecl = false;
951 return sb.ToString ();
955 public class DTDNotationDeclaration : DTDNode
965 set { name = value; }
968 public string PublicId {
969 get { return publicId; }
970 set { publicId = value; }
973 public string SystemId {
974 get { return systemId; }
975 set { systemId = value; }
978 public string LocalName {
979 get { return localName; }
980 set { localName = value; }
983 public string Prefix {
984 get { return prefix; }
985 set { prefix = value; }
988 internal DTDNotationDeclaration (DTDObjectModel root)
994 public class DTDParameterEntityDeclarationCollection
996 Hashtable peDecls = new Hashtable ();
999 public DTDParameterEntityDeclarationCollection (DTDObjectModel root)
1004 public DTDParameterEntityDeclaration this [string name] {
1005 get { return peDecls [name] as DTDParameterEntityDeclaration; }
1008 public void Add (string name, DTDParameterEntityDeclaration decl)
1010 if (peDecls [name] != null) {
1011 // this.root.AddError (new XmlSchemaException (String.Format (
1012 // "Parameter entity declaration for {0} was already added.",
1016 decl.SetRoot (root);
1017 peDecls.Add (name, decl);
1020 public ICollection Keys {
1021 get { return peDecls.Keys; }
1024 public ICollection Values {
1025 get { return peDecls.Values; }
1029 public class DTDParameterEntityDeclaration : DTDEntityBase
1031 string resolvedValue;
1032 Exception loadException;
1035 public bool LoadFailed {
1036 get { return loadFailed; }
1037 set { loadFailed = value; }
1040 public string Value {
1042 if (LiteralEntityValue != null)
1043 return LiteralEntityValue;
1044 if (resolvedValue == null)
1045 throw new InvalidOperationException ();
1046 return resolvedValue;
1050 public void Resolve (XmlResolver resolver)
1052 if (resolver == null) {
1053 resolvedValue = String.Empty;
1060 if (BaseURI != null && BaseURI.Length > 0)
1061 baseUri = new Uri (BaseURI);
1062 } catch (UriFormatException) {
1065 Uri absUri = resolver.ResolveUri (baseUri, SystemId);
1066 string absPath = absUri.ToString ();
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) {
1075 resolvedValue = String.Empty;
1081 public enum DTDContentOrderType
1088 public enum DTDAttributeOccurenceType
1096 public enum DTDOccurence