2 // System.Xml.XmlTextReader
5 // Jason Diamond (jason@injektilo.org)
7 // (C) 2001, 2002 Jason Diamond http://injektilo.org/
11 // This can only parse basic XML: elements, attributes, processing
12 // instructions, and comments are OK.
14 // It barfs on DOCTYPE declarations.
16 // There's also no checking being done for either well-formedness
19 // NameTables aren't being used everywhere yet.
21 // Some thought needs to be given to performance. There's too many
22 // strings being allocated.
24 // Some of the MoveTo methods haven't been implemented yet.
26 // LineNumber and LinePosition aren't being tracked.
28 // xml:space, xml:lang, and xml:base aren't being tracked.
32 using System.Collections;
38 public class XmlTextReader : XmlReader, IXmlLineInfo
43 protected XmlTextReader ()
45 throw new NotImplementedException ();
49 public XmlTextReader (Stream input)
51 throw new NotImplementedException ();
55 public XmlTextReader (string url)
57 throw new NotImplementedException ();
61 public XmlTextReader (TextReader input)
63 XmlNameTable nt = new NameTable ();
64 XmlNamespaceManager nsMgr = new XmlNamespaceManager (nt);
65 parserContext = new XmlParserContext (null, nsMgr, null, XmlSpace.None);
71 protected XmlTextReader (XmlNameTable nt)
73 throw new NotImplementedException ();
77 public XmlTextReader (Stream input, XmlNameTable nt)
79 throw new NotImplementedException ();
83 public XmlTextReader (string url, Stream input)
85 throw new NotImplementedException ();
89 public XmlTextReader (string url, TextReader input)
91 throw new NotImplementedException ();
95 public XmlTextReader (string url, XmlNameTable nt)
97 throw new NotImplementedException ();
101 public XmlTextReader (TextReader input, XmlNameTable nt)
103 throw new NotImplementedException ();
107 public XmlTextReader (Stream xmlFragment, XmlNodeType fragType, XmlParserContext context)
109 throw new NotImplementedException ();
113 public XmlTextReader (string url, Stream input, XmlNameTable nt)
115 throw new NotImplementedException ();
119 public XmlTextReader (string url, TextReader input, XmlNameTable nt)
121 throw new NotImplementedException ();
125 public XmlTextReader (string xmlFragment, XmlNodeType fragType, XmlParserContext context)
127 throw new NotImplementedException ();
134 public override int AttributeCount
136 get { return attributes.Count; }
140 public override string BaseURI
142 get { throw new NotImplementedException (); }
145 public override int Depth
147 get { return depth > 0 ? depth : 0; }
151 public Encoding Encoding
153 get { throw new NotImplementedException (); }
156 public override bool EOF
161 readState == ReadState.EndOfFile ||
162 readState == ReadState.Closed;
166 public override bool HasValue
168 get { return value != String.Empty; }
171 public override bool IsDefault
175 // XmlTextReader does not expand default attributes.
180 public override bool IsEmptyElement
182 get { return isEmptyElement; }
185 public override string this [int i]
187 get { return GetAttribute (i); }
190 public override string this [string name]
192 get { return GetAttribute (name); }
195 public override string this [string localName, string namespaceName]
197 get { return GetAttribute (localName, namespaceName); }
201 public int LineNumber
203 get { throw new NotImplementedException (); }
207 public int LinePosition
209 get { throw new NotImplementedException (); }
212 public override string LocalName
214 get { return localName; }
217 public override string Name
223 public bool Namespaces
225 get { throw new NotImplementedException (); }
226 set { throw new NotImplementedException (); }
229 public override string NamespaceURI
231 get { return namespaceURI; }
234 public override XmlNameTable NameTable
236 get { return parserContext.NameTable; }
239 public override XmlNodeType NodeType
241 get { return nodeType; }
245 public bool Normalization
247 get { throw new NotImplementedException (); }
248 set { throw new NotImplementedException (); }
251 public override string Prefix
253 get { return prefix; }
257 public override char QuoteChar
259 get { throw new NotImplementedException (); }
262 public override ReadState ReadState
264 get { return readState; }
267 public override string Value
269 get { return value; }
273 public WhitespaceHandling WhitespaceHandling
275 get { throw new NotImplementedException (); }
276 set { throw new NotImplementedException (); }
280 public override string XmlLang
282 get { throw new NotImplementedException (); }
286 public XmlResolver XmlResolver
288 set { throw new NotImplementedException (); }
292 public override XmlSpace XmlSpace
294 get { throw new NotImplementedException (); }
302 public override void Close ()
304 readState = ReadState.Closed;
308 public override string GetAttribute (int i)
310 throw new NotImplementedException ();
313 public override string GetAttribute (string name)
315 return attributes [name] as string;
318 public override string GetAttribute (string localName, string namespaceURI)
320 foreach (DictionaryEntry entry in attributes)
322 string thisName = entry.Key as string;
324 int indexOfColon = thisName.IndexOf (':');
326 if (indexOfColon != -1) {
327 string thisLocalName = thisName.Substring (indexOfColon + 1);
329 if (localName == thisLocalName) {
330 string thisPrefix = thisName.Substring (0, indexOfColon);
331 string thisNamespaceURI = LookupNamespace (thisPrefix);
333 if (namespaceURI == thisNamespaceURI)
334 return attributes [thisName] as string;
336 } else if (localName == "xmlns" && namespaceURI == "http://www.w3.org/2000/xmlns/" && thisName == "xmlns")
337 return attributes [thisName] as string;
344 public TextReader GetRemainder ()
346 throw new NotImplementedException ();
350 bool IXmlLineInfo.HasLineInfo ()
352 throw new NotImplementedException ();
355 public override string LookupNamespace (string prefix)
357 return parserContext.NamespaceManager.LookupNamespace (prefix);
361 public override void MoveToAttribute (int i)
363 throw new NotImplementedException ();
367 public override bool MoveToAttribute (string name)
369 throw new NotImplementedException ();
373 public override bool MoveToAttribute (string localName, string namespaceName)
375 throw new NotImplementedException ();
378 public override bool MoveToElement ()
380 if (nodeType == XmlNodeType.Attribute) {
381 RestoreProperties ();
388 public override bool MoveToFirstAttribute ()
391 return MoveToNextAttribute ();
394 public override bool MoveToNextAttribute ()
396 if (attributes == null)
399 if (attributeEnumerator == null) {
401 attributeEnumerator = attributes.GetEnumerator ();
404 if (attributeEnumerator.MoveNext ()) {
405 string name = attributeEnumerator.Key as string;
406 string value = attributeEnumerator.Value as string;
408 XmlNodeType.Attribute, // nodeType
410 false, // isEmptyElement
412 false // clearAttributes
420 public override bool Read ()
424 readState = ReadState.Interactive;
426 more = ReadContent ();
432 public override bool ReadAttributeValue ()
434 throw new NotImplementedException ();
438 public int ReadBase64 (byte [] buffer, int offset, int length)
440 throw new NotImplementedException ();
444 public int ReadBinHex (byte [] buffer, int offset, int length)
446 throw new NotImplementedException ();
450 public int ReadChars (char [] buffer, int offset, int length)
452 throw new NotImplementedException ();
456 public override string ReadInnerXml ()
458 throw new NotImplementedException ();
462 public override string ReadOuterXml ()
464 throw new NotImplementedException ();
468 public override string ReadString ()
470 throw new NotImplementedException ();
474 public void ResetState ()
476 throw new NotImplementedException ();
479 public override void ResolveEntity ()
481 // XmlTextReaders don't resolve entities.
482 throw new InvalidOperationException ("XmlTextReaders don't resolve entities.");
489 private XmlParserContext parserContext;
491 private TextReader reader;
492 private ReadState readState;
495 private bool depthDown;
497 private bool popScope;
499 private XmlNodeType nodeType;
501 private string prefix;
502 private string localName;
503 private string namespaceURI;
504 private bool isEmptyElement;
505 private string value;
507 private XmlNodeType saveNodeType;
508 private string saveName;
509 private string savePrefix;
510 private string saveLocalName;
511 private string saveNamespaceURI;
512 private bool saveIsEmptyElement;
514 private Hashtable attributes;
515 private IDictionaryEnumerator attributeEnumerator;
517 private bool returnEntityReference;
518 private string entityReferenceName;
520 private char [] nameBuffer;
521 private int nameLength;
522 private int nameCapacity;
523 private const int initialNameCapacity = 256;
525 private char [] valueBuffer;
526 private int valueLength;
527 private int valueCapacity;
528 private const int initialValueCapacity = 8192;
532 readState = ReadState.Initial;
539 nodeType = XmlNodeType.None;
541 prefix = String.Empty;
542 localName = string.Empty;
543 isEmptyElement = false;
544 value = String.Empty;
546 attributes = new Hashtable ();
547 attributeEnumerator = null;
549 returnEntityReference = false;
550 entityReferenceName = String.Empty;
552 nameBuffer = new char [initialNameCapacity];
554 nameCapacity = initialNameCapacity;
556 valueBuffer = new char [initialValueCapacity];
558 valueCapacity = initialValueCapacity;
561 // Use this method rather than setting the properties
562 // directly so that all the necessary properties can
563 // be changed in harmony with each other. Maybe the
564 // fields should be in a seperate class to help enforce
566 private void SetProperties (
567 XmlNodeType nodeType,
571 bool clearAttributes)
573 this.nodeType = nodeType;
575 this.isEmptyElement = isEmptyElement;
581 int indexOfColon = name.IndexOf (':');
583 if (indexOfColon == -1) {
584 prefix = String.Empty;
587 prefix = name.Substring (0, indexOfColon);
588 localName = name.Substring (indexOfColon + 1);
591 namespaceURI = LookupNamespace (prefix);
594 private void SaveProperties ()
596 saveNodeType = nodeType;
599 saveLocalName = localName;
600 saveNamespaceURI = namespaceURI;
601 saveIsEmptyElement = isEmptyElement;
602 // An element's value is always String.Empty.
605 private void RestoreProperties ()
607 nodeType = saveNodeType;
610 localName = saveLocalName;
611 namespaceURI = saveNamespaceURI;
612 isEmptyElement = saveIsEmptyElement;
613 value = String.Empty;
616 private void AddAttribute (string name, string value)
618 attributes.Add (name, value);
621 private void ClearAttributes ()
623 if (attributes.Count > 0)
626 attributeEnumerator = null;
629 private int PeekChar ()
631 return reader.Peek ();
634 private int ReadChar ()
636 return reader.Read ();
639 // This should really keep track of some state so
640 // that it's not possible to have more than one document
641 // element or text outside of the document element.
642 private bool ReadContent ()
647 parserContext.NamespaceManager.PopScope ();
654 if (returnEntityReference) {
656 SetEntityReferenceProperties ();
667 readState = ReadState.EndOfFile;
669 XmlNodeType.None, // nodeType
670 String.Empty, // name
671 false, // isEmptyElement
672 String.Empty, // value
673 true // clearAttributes
687 private void SetEntityReferenceProperties ()
690 XmlNodeType.EntityReference, // nodeType
691 entityReferenceName, // name
692 false, // isEmptyElement
693 String.Empty, // value
694 true // clearAttributes
697 returnEntityReference = false;
698 entityReferenceName = String.Empty;
701 // The leading '<' has already been consumed.
702 private void ReadTag ()
712 ReadProcessingInstruction ();
724 // The leading '<' has already been consumed.
725 private void ReadStartTag ()
727 parserContext.NamespaceManager.PushScope ();
729 string name = ReadName ();
732 bool isEmptyElement = false;
736 if (XmlChar.IsFirstNameChar (PeekChar ()))
739 if (PeekChar () == '/') {
741 isEmptyElement = true;
751 XmlNodeType.Element, // nodeType
753 isEmptyElement, // isEmptyElement
754 String.Empty, // value
755 false // clearAttributes
759 // The reader is positioned on the first character
760 // of the element's name.
761 private void ReadEndTag ()
763 string name = ReadName ();
770 XmlNodeType.EndElement, // nodeType
772 false, // isEmptyElement
773 String.Empty, // value
774 true // clearAttributes
780 private void AppendNameChar (int ch)
782 CheckNameCapacity ();
783 nameBuffer [nameLength++] = (char)ch;
786 private void CheckNameCapacity ()
788 if (nameLength == nameCapacity) {
789 nameCapacity = nameCapacity * 2;
790 char [] oldNameBuffer = nameBuffer;
791 nameBuffer = new char [nameCapacity];
792 Array.Copy (oldNameBuffer, nameBuffer, nameLength);
796 private string CreateNameString ()
798 return new String (nameBuffer, 0, nameLength);
801 private void AppendValueChar (int ch)
803 CheckValueCapacity ();
804 valueBuffer [valueLength++] = (char)ch;
807 private void CheckValueCapacity ()
809 if (valueLength == valueCapacity) {
810 valueCapacity = valueCapacity * 2;
811 char [] oldValueBuffer = valueBuffer;
812 valueBuffer = new char [valueCapacity];
813 Array.Copy (oldValueBuffer, valueBuffer, valueLength);
817 private string CreateValueString ()
819 return new String (valueBuffer, 0, valueLength);
822 // The reader is positioned on the first character
824 private void ReadText ()
828 int ch = PeekChar ();
830 while (ch != '<' && ch != -1) {
833 if (ReadReference (false))
836 AppendValueChar (ReadChar ());
841 if (returnEntityReference && valueLength == 0) {
843 SetEntityReferenceProperties ();
851 XmlNodeType.Text, // nodeType
852 String.Empty, // name
853 false, // isEmptyElement
854 CreateValueString (), // value
855 true // clearAttributes
860 // The leading '&' has already been consumed.
861 // Returns true if the entity reference isn't a simple
862 // character reference or one of the predefined entities.
863 // This allows the ReadText method to break so that the
864 // next call to Read will return the EntityReference node.
865 private bool ReadReference (bool ignoreEntityReferences)
867 if (PeekChar () == '#') {
869 ReadCharacterReference ();
871 ReadEntityReference (ignoreEntityReferences);
873 return returnEntityReference;
876 private void ReadCharacterReference ()
880 if (PeekChar () == 'x') {
883 while (PeekChar () != ';' && PeekChar () != -1) {
884 int ch = ReadChar ();
886 if (ch >= '0' && ch <= '9')
887 value = (value << 4) + ch - '0';
888 else if (ch >= 'A' && ch <= 'F')
889 value = (value << 4) + ch - 'A' + 10;
890 else if (ch >= 'a' && ch <= 'f')
891 value = (value << 4) + ch - 'a' + 10;
893 throw new XmlException (
895 "invalid hexadecimal digit: {0} (#x{1:X})",
900 while (PeekChar () != ';' && PeekChar () != -1) {
901 int ch = ReadChar ();
903 if (ch >= '0' && ch <= '9')
904 value = value * 10 + ch - '0';
906 throw new XmlException (
908 "invalid decimal digit: {0} (#x{1:X})",
916 AppendValueChar (value);
919 private void ReadEntityReference (bool ignoreEntityReferences)
923 int ch = PeekChar ();
925 while (ch != ';' && ch != -1) {
926 AppendNameChar (ReadChar ());
932 string name = CreateNameString ();
937 AppendValueChar ('<');
940 AppendValueChar ('>');
943 AppendValueChar ('&');
946 AppendValueChar ('\'');
949 AppendValueChar ('"');
952 if (ignoreEntityReferences) {
953 AppendValueChar ('&');
955 foreach (char ch2 in name) {
956 AppendValueChar (ch2);
959 AppendValueChar (';');
961 returnEntityReference = true;
962 entityReferenceName = name;
968 // The reader is positioned on the first character of
969 // the attribute name.
970 private void ReadAttributes ()
973 string name = ReadName ();
977 string value = ReadAttribute ();
981 parserContext.NamespaceManager.AddNamespace (String.Empty, value);
982 else if (name.StartsWith ("xmlns:"))
983 parserContext.NamespaceManager.AddNamespace (name.Substring (6), value);
985 AddAttribute (name, value);
986 } while (PeekChar () != '/' && PeekChar () != '>' && PeekChar () != -1);
989 // The reader is positioned on the quote character.
990 private string ReadAttribute ()
992 int quoteChar = ReadChar ();
994 if (quoteChar != '\'' && quoteChar != '\"')
995 throw new XmlException ("an attribute value was not quoted");
999 while (PeekChar () != quoteChar) {
1000 int ch = ReadChar ();
1005 throw new XmlException ("attribute values cannot contain '<'");
1007 ReadReference (true);
1010 throw new XmlException ("unexpected end of file in an attribute value");
1012 AppendValueChar (ch);
1017 ReadChar (); // quoteChar
1019 return CreateValueString ();
1022 // The reader is positioned on the first character
1024 private void ReadProcessingInstruction ()
1026 string target = ReadName ();
1031 while (PeekChar () != -1) {
1032 int ch = ReadChar ();
1034 if (ch == '?' && PeekChar () == '>') {
1039 AppendValueChar ((char)ch);
1043 XmlNodeType.ProcessingInstruction, // nodeType
1045 false, // isEmptyElement
1046 CreateValueString (), // value
1047 true // clearAttributes
1051 // The reader is positioned on the first character after
1052 // the leading '<!'.
1053 private void ReadDeclaration ()
1055 int ch = PeekChar ();
1077 // The reader is positioned on the first character after
1078 // the leading '<!--'.
1079 private void ReadComment ()
1083 while (PeekChar () != -1) {
1084 int ch = ReadChar ();
1086 if (ch == '-' && PeekChar () == '-') {
1089 if (PeekChar () != '>')
1090 throw new XmlException ("comments cannot contain '--'");
1096 AppendValueChar ((char)ch);
1100 XmlNodeType.Comment, // nodeType
1101 String.Empty, // name
1102 false, // isEmptyElement
1103 CreateValueString (), // value
1104 true // clearAttributes
1108 // The reader is positioned on the first character after
1109 // the leading '<![CDATA['.
1110 private void ReadCDATA ()
1114 while (PeekChar () != -1) {
1115 int ch = ReadChar ();
1117 if (ch == ']' && PeekChar () == ']') {
1118 ch = ReadChar (); // ']'
1120 if (PeekChar () == '>') {
1124 AppendValueChar (']');
1125 AppendValueChar (']');
1130 AppendValueChar ((char)ch);
1136 XmlNodeType.CDATA, // nodeType
1137 String.Empty, // name
1138 false, // isEmptyElement
1139 CreateValueString (), // value
1140 true // clearAttributes
1144 // The reader is positioned on the first character
1146 private string ReadName ()
1148 if (!XmlChar.IsFirstNameChar (PeekChar ()))
1149 throw new XmlException ("a name did not start with a legal character");
1153 AppendNameChar (ReadChar ());
1155 while (XmlChar.IsNameChar (PeekChar ())) {
1156 AppendNameChar (ReadChar ());
1159 return CreateNameString ();
1162 // Read the next character and compare it against the
1163 // specified character.
1164 private void Expect (int expected)
1166 int ch = ReadChar ();
1168 if (ch != expected) {
1169 throw new XmlException (
1171 "expected '{0}' ({1:X}) but found '{2}' ({3:X})",
1179 // Does not consume the first non-whitespace character.
1180 private void SkipWhitespace ()
1182 while (XmlChar.IsWhitespace (PeekChar ()))