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 DTDElementDeclarationCollection elementDecls;
25 DTDAttListDeclarationCollection attListDecls;
26 DTDEntityDeclarationCollection entityDecls;
27 DTDNotationDeclarationCollection notationDecls;
28 ArrayList validationErrors;
31 public DTDObjectModel ()
33 elementDecls = new DTDElementDeclarationCollection (this);
34 attListDecls = new DTDAttListDeclarationCollection (this);
35 entityDecls = new DTDEntityDeclarationCollection (this);
36 notationDecls = new DTDNotationDeclarationCollection (this);
37 factory = new DTDAutomataFactory (this);
38 validationErrors = new ArrayList ();
41 public string BaseURI;
45 public string PublicId;
47 public string SystemId;
49 public string InternalSubset;
51 public bool InternalSubsetHasPEReference;
53 public string ResolveEntity (string name)
55 DTDEntityDeclaration decl = EntityDecls [name]
56 as DTDEntityDeclaration;
57 return decl.EntityValue;
60 internal XmlResolver Resolver {
61 get { return resolver; }
64 public XmlResolver XmlResolver {
65 set { resolver = value; }
68 private DTDAutomataFactory factory;
69 public DTDAutomataFactory Factory {
70 get { return factory; }
73 public DTDElementDeclaration RootElement {
74 get { return ElementDecls [Name]; }
77 public DTDElementDeclarationCollection ElementDecls {
78 get { return elementDecls; }
81 public DTDAttListDeclarationCollection AttListDecls {
82 get { return attListDecls; }
85 public DTDEntityDeclarationCollection EntityDecls {
86 get { return entityDecls; }
89 public DTDNotationDeclarationCollection NotationDecls {
90 get { return notationDecls; }
93 DTDElementAutomata rootAutomata;
94 public DTDAutomata RootAutomata {
96 if (rootAutomata == null)
97 rootAutomata = new DTDElementAutomata (this, this.Name);
102 DTDEmptyAutomata emptyAutomata;
103 public DTDEmptyAutomata Empty {
105 if (emptyAutomata == null)
106 emptyAutomata = new DTDEmptyAutomata (this);
107 return emptyAutomata;
111 DTDAnyAutomata anyAutomata;
112 public DTDAnyAutomata Any {
114 if (anyAutomata == null)
115 anyAutomata = new DTDAnyAutomata (this);
120 DTDInvalidAutomata invalidAutomata;
121 public DTDInvalidAutomata Invalid {
123 if (invalidAutomata == null)
124 invalidAutomata = new DTDInvalidAutomata (this);
125 return invalidAutomata;
129 public XmlSchemaException [] Errors {
130 get { return validationErrors.ToArray (typeof (XmlSchemaException)) as XmlSchemaException []; }
133 public void AddError (XmlSchemaException ex)
135 validationErrors.Add (ex);
139 public class DTDElementDeclarationCollection
141 Hashtable elementDecls = new Hashtable ();
144 public DTDElementDeclarationCollection (DTDObjectModel root)
149 public DTDElementDeclaration this [string name] {
150 get { return elementDecls [name] as DTDElementDeclaration; }
153 public void Add (string name, DTDElementDeclaration decl)
155 if (elementDecls [name] != null) {
156 this.root.AddError (new XmlSchemaException (String.Format (
157 "Element declaration for {0} was already added.",
162 elementDecls.Add (name, decl);
165 public ICollection Keys {
166 get { return elementDecls.Keys; }
169 public ICollection Values {
170 get { return elementDecls.Values; }
174 public class DTDAttListDeclarationCollection
176 Hashtable attListDecls = new Hashtable ();
179 public DTDAttListDeclarationCollection (DTDObjectModel root)
184 public DTDAttListDeclaration this [string name] {
185 get { return attListDecls [name] as DTDAttListDeclaration; }
188 public void Add (string name, DTDAttListDeclaration decl)
190 DTDAttListDeclaration existing = this [name];
191 if (existing != null) {
192 // It should be valid and
193 // has effect of additive declaration.
194 foreach (DTDAttributeDefinition def in decl.Definitions)
195 if (decl.Get (def.Name) == null)
199 attListDecls.Add (name, decl);
203 public ICollection Keys {
204 get { return attListDecls.Keys; }
207 public ICollection Values {
208 get { return attListDecls.Values; }
212 public class DTDEntityDeclarationCollection
214 Hashtable entityDecls = new Hashtable ();
217 public DTDEntityDeclarationCollection (DTDObjectModel root)
222 public DTDEntityDeclaration this [string name] {
223 get { return entityDecls [name] as DTDEntityDeclaration; }
226 public void Add (string name, DTDEntityDeclaration decl)
228 if (entityDecls [name] != null)
229 throw new InvalidOperationException (String.Format (
230 "Entity declaration for {0} was already added.",
233 entityDecls.Add (name, decl);
236 public ICollection Keys {
237 get { return entityDecls.Keys; }
240 public ICollection Values {
241 get { return entityDecls.Values; }
245 public class DTDNotationDeclarationCollection
247 Hashtable notationDecls = new Hashtable ();
250 public DTDNotationDeclarationCollection (DTDObjectModel root)
255 public DTDNotationDeclaration this [string name] {
256 get { return notationDecls [name] as DTDNotationDeclaration; }
259 public void Add (string name, DTDNotationDeclaration decl)
261 if (notationDecls [name] != null)
262 throw new InvalidOperationException (String.Format (
263 "Notation declaration for {0} was already added.",
266 notationDecls.Add (name, decl);
269 public ICollection Keys {
270 get { return notationDecls.Keys; }
273 public ICollection Values {
274 get { return notationDecls.Values; }
278 public class DTDContentModel
280 private DTDObjectModel root;
281 DTDAutomata compiledAutomata;
283 private string ownerElementName;
284 public string ElementName;
285 public DTDContentOrderType OrderType = DTDContentOrderType.None;
286 public DTDContentModelCollection ChildModels
287 = new DTDContentModelCollection ();
288 public DTDOccurence Occurence = DTDOccurence.One;
290 internal DTDContentModel (DTDObjectModel root, string ownerElementName)
293 this.ownerElementName = ownerElementName;
296 public DTDElementDeclaration ElementDecl {
298 return root.ElementDecls [ownerElementName];
302 public DTDAutomata GetAutomata ()
304 if (compiledAutomata == null)
306 return compiledAutomata;
309 public DTDAutomata Compile ()
311 compiledAutomata = CompileInternal ();
312 return compiledAutomata;
315 private DTDAutomata CompileInternal ()
317 if (ElementDecl.IsAny)
319 if (ElementDecl.IsEmpty)
322 DTDAutomata basis = GetBasicContentAutomata ();
324 case DTDOccurence.One:
326 case DTDOccurence.Optional:
327 return Choice (root.Empty, basis);
328 case DTDOccurence.OneOrMore:
329 return new DTDOneOrMoreAutomata (root, basis);
330 case DTDOccurence.ZeroOrMore:
331 return Choice (root.Empty, new DTDOneOrMoreAutomata (root, basis));
333 throw new InvalidOperationException ();
336 private DTDAutomata GetBasicContentAutomata ()
338 if (ElementName != null)
339 return new DTDElementAutomata (root, ElementName);
340 switch (ChildModels.Count) {
344 return ChildModels [0].GetAutomata ();
347 DTDAutomata current = null;
348 int childCount = ChildModels.Count;
350 case DTDContentOrderType.Seq:
352 ChildModels [childCount - 2].GetAutomata (),
353 ChildModels [childCount - 1].GetAutomata ());
354 for (int i = childCount - 2; i > 0; i--)
356 ChildModels [i - 1].GetAutomata (), current);
358 case DTDContentOrderType.Or:
360 ChildModels [childCount - 2].GetAutomata (),
361 ChildModels [childCount - 1].GetAutomata ());
362 for (int i = childCount - 2; i > 0; i--)
364 ChildModels [i - 1].GetAutomata (), current);
367 throw new InvalidOperationException ("Invalid pattern specification");
371 private DTDAutomata Sequence (DTDAutomata l, DTDAutomata r)
373 return root.Factory.Sequence (l, r);
376 private DTDAutomata Choice (DTDAutomata l, DTDAutomata r)
378 return l.MakeChoice (r);
383 public class DTDContentModelCollection
385 ArrayList contentModel = new ArrayList ();
387 public DTDContentModelCollection ()
391 public DTDContentModel this [int i] {
392 get { return contentModel [i] as DTDContentModel; }
396 get { return contentModel.Count; }
399 public void Add (DTDContentModel model)
401 contentModel.Add (model);
405 public abstract class DTDNode
407 private DTDObjectModel root;
408 public string BaseURI;
409 public int LineNumber;
410 public int LinePosition;
412 internal void SetRoot (DTDObjectModel root)
416 this.BaseURI = root.BaseURI;
419 protected DTDObjectModel Root {
424 public class DTDElementDeclaration : DTDNode // : ICloneable
429 public bool IsMixedContent;
430 public DTDContentModel contentModel;
433 internal DTDElementDeclaration (DTDObjectModel root)
438 public DTDContentModel ContentModel {
440 if (contentModel == null)
441 contentModel = new DTDContentModel (root, Name);
446 public DTDAttListDeclaration Attributes {
448 return Root.AttListDecls [Name];
452 // public object Clone ()
454 // return this.MemberwiseClone ();
458 public class DTDAttributeDefinition : DTDNode// : ICloneable
461 public XmlSchemaDatatype Datatype;
462 // entity reference inside enumerated values are not allowed,
463 // but on the other hand, they are allowed inside default value.
464 // Then I decided to use string ArrayList for enumerated values,
465 // and unresolved string value for DefaultValue.
466 public ArrayList EnumeratedAttributeDeclaration = new ArrayList ();
467 public string UnresolvedDefaultValue = null;
468 public ArrayList EnumeratedNotations = new ArrayList();
469 public DTDAttributeOccurenceType OccurenceType = DTDAttributeOccurenceType.None;
470 private string resolvedDefaultValue;
471 private string resolvedNormalizedDefaultValue;
473 internal DTDAttributeDefinition () {}
475 public string DefaultValue {
477 if (resolvedDefaultValue == null)
478 resolvedDefaultValue = ComputeDefaultValue ();
479 return resolvedDefaultValue;
483 public string NormalizedDefaultValue {
485 if (resolvedNormalizedDefaultValue == null) {
486 object o = Datatype.ParseValue (ComputeDefaultValue (), null, null);
487 resolvedNormalizedDefaultValue =
489 String.Join (" ", (string []) o) :
492 return resolvedNormalizedDefaultValue;
496 private string ComputeDefaultValue ()
498 if (UnresolvedDefaultValue == null)
501 StringBuilder sb = new StringBuilder ();
504 string value = this.UnresolvedDefaultValue;
505 while ((next = value.IndexOf ('&', pos)) >= 0) {
506 int semicolon = value.IndexOf (';', next);
507 if (value [next + 1] == '#') {
508 // character reference.
509 char c = value [next + 2];
510 NumberStyles style = NumberStyles.Integer;
512 if (c == 'x' || c == 'X') {
513 spec = value.Substring (next + 3, semicolon - next - 3);
514 style |= NumberStyles.HexNumber;
517 spec = value.Substring (next + 2, semicolon - next - 2);
518 sb.Append ((char) int.Parse (spec, style));
520 sb.Append (value.Substring (pos, next - 1));
521 string name = value.Substring (pos + 1, semicolon - 1);
522 char predefined = XmlChar.GetPredefinedEntity (name);
524 sb.Append (predefined);
526 sb.Append (Root.ResolveEntity (name));
530 sb.Append (value.Substring (pos));
532 string ret = sb.ToString (1, sb.Length - 2);
537 public char QuoteChar {
539 return UnresolvedDefaultValue.Length > 0 ?
540 this.UnresolvedDefaultValue [0] :
545 // public object Clone ()
547 // return this.MemberwiseClone ();
551 public class DTDAttListDeclaration : DTDNode // : ICloneable
555 internal DTDAttListDeclaration (DTDObjectModel root)
560 private Hashtable attributeOrders = new Hashtable ();
561 private ArrayList attributes = new ArrayList ();
563 public DTDAttributeDefinition this [int i] {
564 get { return Get (i); }
567 public DTDAttributeDefinition this [string name] {
568 get { return Get (name); }
571 public DTDAttributeDefinition Get (int i)
573 return attributes [i] as DTDAttributeDefinition;
576 public DTDAttributeDefinition Get (string name)
578 object o = attributeOrders [name];
580 return attributes [(int) o] as DTDAttributeDefinition;
585 public ICollection Definitions {
586 get { return attributes; }
589 public void Add (DTDAttributeDefinition def)
591 if (attributeOrders [def.Name] != null)
592 throw new InvalidOperationException (String.Format (
593 "Attribute definition for {0} was already added at element {1}.",
594 def.Name, this.Name));
596 attributeOrders.Add (def.Name, attributes.Count);
597 attributes.Add (def);
601 get { return attributeOrders.Count; }
604 // public object Clone ()
606 // return this.MemberwiseClone ();
610 public class DTDEntityDeclaration : DTDNode
615 public string PublicId;
616 public string SystemId;
617 public string NotationName;
618 public string LiteralEntityValue;
619 public bool IsInternalSubset;
620 public StringCollection ReferencingEntities = new StringCollection ();
624 public string EntityValue {
626 if (entityValue == null) {
627 if (NotationName != null)
629 else if (SystemId == null)
630 entityValue = LiteralEntityValue;
632 // FIXME: should use specified XmlUrlResolver.
633 entityValue = ResolveExternalEntity (Root.Resolver);
635 // Check illegal recursion.
636 ScanEntityValue (new StringCollection ());
642 public void ScanEntityValue (StringCollection refs)
644 // To modify this code, beware nesting between this and EntityValue.
645 string value = EntityValue;
648 throw new XmlException ("Entity recursion was found.");
652 foreach (string referenced in refs)
653 if (this.ReferencingEntities.Contains (referenced))
654 throw new XmlException (String.Format (
655 "Nested entity was found between {0} and {1}",
661 int len = value.Length;
663 for (int i=0; i<len; i++) {
671 string name = value.Substring (start, i - start);
672 this.ReferencingEntities.Add (name);
673 DTDEntityDeclaration decl = Root.EntityDecls [name];
676 decl.ScanEntityValue (refs);
677 foreach (string str in decl.ReferencingEntities)
678 ReferencingEntities.Add (str);
689 private string ResolveExternalEntity (XmlResolver resolver)
691 if (resolver == null)
694 string baseUri = Root.BaseURI;
697 Uri uri = resolver.ResolveUri (
698 baseUri != null ? new Uri (baseUri) : null, SystemId);
699 Stream stream = resolver.GetEntity (uri, null, typeof (Stream)) as Stream;
700 XmlStreamReader reader = new XmlStreamReader (stream, false);
702 StringBuilder sb = new StringBuilder ();
704 bool checkTextDecl = true;
705 while (reader.Peek () != -1) {
706 sb.Append ((char) reader.Read ());
707 if (checkTextDecl && sb.Length == 6) {
708 if (sb.ToString () == "<?xml ") {
709 // Skip Text declaration.
711 StringBuilder textdecl = new StringBuilder ();
712 while (reader.Peek () != '>' && reader.Peek () != -1)
713 textdecl.Append ((char) reader.Read ());
714 if (textdecl.ToString ().IndexOf ("encoding") < 0)
715 throw new XmlException ("Text declaration must have encoding specification: " + BaseURI);
716 if (textdecl.ToString ().IndexOf ("standalone") >= 0)
717 throw new XmlException ("Text declaration cannot have standalone declaration: " + BaseURI);
719 checkTextDecl = false;
722 return sb.ToString ();
725 internal DTDEntityDeclaration (DTDObjectModel root)
731 public class DTDNotationDeclaration : DTDNode
734 public string LocalName;
735 public string Prefix;
736 public string PublicId;
737 public string SystemId;
739 internal DTDNotationDeclaration () {}
742 public class DTDParameterEntityDeclaration : DTDNode
744 string resolvedValue;
745 Exception loadException;
748 public string PublicId;
749 public string SystemId;
750 public string LiteralValue;
751 public bool LoadFailed;
753 public string Value {
755 if (LiteralValue != null)
757 if (resolvedValue == null)
758 throw new InvalidOperationException ();
759 return resolvedValue;
763 public void Resolve (XmlResolver resolver)
765 if (resolver == null) {
766 resolvedValue = String.Empty;
773 baseUri = new Uri (BaseURI);
774 } catch (UriFormatException) {
777 Uri absUri = resolver.ResolveUri (baseUri, SystemId);
778 string absPath = absUri.ToString ();
781 XmlStreamReader tw = new XmlStreamReader (absUri.ToString (), false, resolver, BaseURI);
782 string s = tw.ReadToEnd ();
783 if (s.StartsWith ("<?xml")) {
784 int end = s.IndexOf (">") + 1;
786 throw new XmlException (this as IXmlLineInfo,
787 "Inconsistent text declaration markup.");
788 if (s.IndexOf ("encoding", 0, end) < 0)
789 throw new XmlException (this as IXmlLineInfo,
790 "Text declaration must not omit encoding specification.");
791 if (s.IndexOf ("standalone", 0, end) >= 0)
792 throw new XmlException (this as IXmlLineInfo,
793 "Text declaration cannot have standalone declaration.");
794 resolvedValue = s.Substring (end);
798 } catch (IOException ex) {
800 resolvedValue = String.Empty;
806 public enum DTDContentOrderType
813 public enum DTDAttributeOccurenceType
821 public enum DTDOccurence