2 // System.Xml.XmlTextReader
5 // Jason Diamond (jason@injektilo.org)
6 // Adam Treat (manyoso@yahoo.com)
8 // (C) 2001, 2002 Jason Diamond http://injektilo.org/
12 // This can only parse basic XML: elements, attributes, processing
13 // instructions, and comments are OK.
15 // It barfs on DOCTYPE declarations.
17 // There's also no checking being done for either well-formedness
20 // NameTables aren't being used everywhere yet.
22 // Some thought needs to be given to performance. There's too many
23 // strings being allocated.
25 // Some of the MoveTo methods haven't been implemented yet.
27 // LineNumber and LinePosition aren't being tracked.
29 // xml:space, xml:lang, and xml:base aren't being tracked.
33 using System.Collections;
39 public class XmlTextReader : XmlReader, IXmlLineInfo
44 protected XmlTextReader ()
46 throw new NotImplementedException ();
50 public XmlTextReader (Stream input)
52 // We can share some code in the constructors (at least for this one and next 2)
53 XmlNameTable nt = new NameTable ();
54 XmlNamespaceManager nsMgr = new XmlNamespaceManager (nt);
55 parserContext = new XmlParserContext (null, nsMgr, null, XmlSpace.None);
57 reader = new StreamReader (input);
61 public XmlTextReader (string url)
63 XmlNameTable nt = new NameTable ();
64 XmlNamespaceManager nsMgr = new XmlNamespaceManager (nt);
65 parserContext = new XmlParserContext (null, nsMgr, null, XmlSpace.None);
67 reader = new StreamReader(url);
71 public XmlTextReader (TextReader input)
73 XmlNameTable nt = new NameTable ();
74 XmlNamespaceManager nsMgr = new XmlNamespaceManager (nt);
75 parserContext = new XmlParserContext (null, nsMgr, null, XmlSpace.None);
81 protected XmlTextReader (XmlNameTable nt)
83 throw new NotImplementedException ();
87 public XmlTextReader (Stream input, XmlNameTable nt)
89 throw new NotImplementedException ();
93 public XmlTextReader (string url, Stream input)
95 throw new NotImplementedException ();
99 public XmlTextReader (string url, TextReader input)
101 throw new NotImplementedException ();
105 public XmlTextReader (string url, XmlNameTable nt)
107 throw new NotImplementedException ();
111 public XmlTextReader (TextReader input, XmlNameTable nt)
113 throw new NotImplementedException ();
117 public XmlTextReader (Stream xmlFragment, XmlNodeType fragType, XmlParserContext context)
119 throw new NotImplementedException ();
123 public XmlTextReader (string url, Stream input, XmlNameTable nt)
125 throw new NotImplementedException ();
129 public XmlTextReader (string url, TextReader input, XmlNameTable nt)
131 throw new NotImplementedException ();
135 public XmlTextReader (string xmlFragment, XmlNodeType fragType, XmlParserContext context)
137 //Waiting for Validating reader for fragType rules.
138 parserContext = context;
140 reader = new StringReader(xmlFragment);
147 public override int AttributeCount
149 get { return attributes.Count; }
153 public override string BaseURI
155 get { throw new NotImplementedException (); }
158 public override int Depth
160 get { return depth > 0 ? depth : 0; }
164 public Encoding Encoding
166 get { throw new NotImplementedException (); }
169 public override bool EOF
174 readState == ReadState.EndOfFile ||
175 readState == ReadState.Closed;
179 public override bool HasValue
181 get { return value != String.Empty; }
184 public override bool IsDefault
188 // XmlTextReader does not expand default attributes.
193 public override bool IsEmptyElement
195 get { return isEmptyElement; }
198 public override string this [int i]
200 get { return GetAttribute (i); }
203 public override string this [string name]
205 get { return GetAttribute (name); }
208 public override string this [string localName, string namespaceName]
210 get { return GetAttribute (localName, namespaceName); }
214 public int LineNumber
216 get { throw new NotImplementedException (); }
220 public int LinePosition
222 get { throw new NotImplementedException (); }
225 public override string LocalName
227 get { return localName; }
230 public override string Name
236 public bool Namespaces
238 get { throw new NotImplementedException (); }
239 set { throw new NotImplementedException (); }
242 public override string NamespaceURI
244 get { return namespaceURI; }
247 public override XmlNameTable NameTable
249 get { return parserContext.NameTable; }
252 public override XmlNodeType NodeType
254 get { return nodeType; }
258 public bool Normalization
260 get { throw new NotImplementedException (); }
261 set { throw new NotImplementedException (); }
264 public override string Prefix
266 get { return prefix; }
270 public override char QuoteChar
272 get { throw new NotImplementedException (); }
275 public override ReadState ReadState
277 get { return readState; }
280 public override string Value
282 get { return value; }
286 public WhitespaceHandling WhitespaceHandling
288 get { throw new NotImplementedException (); }
289 set { throw new NotImplementedException (); }
293 public override string XmlLang
295 get { throw new NotImplementedException (); }
299 public XmlResolver XmlResolver
301 set { throw new NotImplementedException (); }
305 public override XmlSpace XmlSpace
307 get { throw new NotImplementedException (); }
315 public override void Close ()
317 readState = ReadState.Closed;
321 public override string GetAttribute (int i)
323 throw new NotImplementedException ();
326 public override string GetAttribute (string name)
328 return attributes [name] as string;
331 public override string GetAttribute (string localName, string namespaceURI)
333 foreach (DictionaryEntry entry in attributes)
335 string thisName = entry.Key as string;
337 int indexOfColon = thisName.IndexOf (':');
339 if (indexOfColon != -1) {
340 string thisLocalName = thisName.Substring (indexOfColon + 1);
342 if (localName == thisLocalName) {
343 string thisPrefix = thisName.Substring (0, indexOfColon);
344 string thisNamespaceURI = LookupNamespace (thisPrefix);
346 if (namespaceURI == thisNamespaceURI)
347 return attributes [thisName] as string;
349 } else if (localName == "xmlns" && namespaceURI == "http://www.w3.org/2000/xmlns/" && thisName == "xmlns")
350 return attributes [thisName] as string;
357 public TextReader GetRemainder ()
359 throw new NotImplementedException ();
363 bool IXmlLineInfo.HasLineInfo ()
368 public override string LookupNamespace (string prefix)
370 return parserContext.NamespaceManager.LookupNamespace (prefix);
374 public override void MoveToAttribute (int i)
376 throw new NotImplementedException ();
379 public override bool MoveToAttribute (string name)
384 if (attributes == null)
387 if (orderedAttributesEnumerator == null) {
389 orderedAttributesEnumerator = orderedAttributes.GetEnumerator ();
392 while (orderedAttributesEnumerator.MoveNext ()) {
393 if(name == orderedAttributesEnumerator.Current as string) {
401 string value = attributes [name] as string;
403 XmlNodeType.Attribute, // nodeType
405 false, // isEmptyElement
407 false // clearAttributes
415 public override bool MoveToAttribute (string localName, string namespaceName)
417 throw new NotImplementedException ();
420 public override bool MoveToElement ()
422 if (orderedAttributesEnumerator != null) {
423 orderedAttributesEnumerator = null;
424 RestoreProperties ();
431 public override bool MoveToFirstAttribute ()
434 return MoveToNextAttribute ();
437 public override bool MoveToNextAttribute ()
439 if (attributes == null)
442 if (orderedAttributesEnumerator == null) {
444 orderedAttributesEnumerator = orderedAttributes.GetEnumerator ();
447 if (orderedAttributesEnumerator.MoveNext ()) {
448 string name = orderedAttributesEnumerator.Current as string;
449 string value = attributes [name] as string;
451 XmlNodeType.Attribute, // nodeType
453 false, // isEmptyElement
455 false // clearAttributes
463 public override bool Read ()
467 readState = ReadState.Interactive;
469 more = ReadContent ();
475 public override bool ReadAttributeValue ()
477 throw new NotImplementedException ();
481 public int ReadBase64 (byte [] buffer, int offset, int length)
483 throw new NotImplementedException ();
487 public int ReadBinHex (byte [] buffer, int offset, int length)
489 throw new NotImplementedException ();
493 public int ReadChars (char [] buffer, int offset, int length)
495 throw new NotImplementedException ();
499 public override string ReadInnerXml ()
501 // Still need a Well Formedness check.
502 // Will wait for Validating reader ;-)
503 if (NodeType == XmlNodeType.Attribute) {
506 saveToXmlBuffer = true;
507 string startname = this.Name;
508 string endname = string.Empty;
509 readState = ReadState.Interactive;
511 while (startname != endname) {
516 xmlBuffer.Replace(currentTag.ToString (), "");
517 saveToXmlBuffer = false;
518 string InnerXml = xmlBuffer.ToString ();
519 xmlBuffer.Length = 0;
525 public override string ReadOuterXml ()
527 // Still need a Well Formedness check.
528 // Will wait for Validating reader ;-)
529 if (NodeType == XmlNodeType.Attribute) {
530 return Name+"=\""+Value+"\"";
532 saveToXmlBuffer = true;
533 xmlBuffer.Append(currentTag.ToString ());
534 string startname = this.Name;
535 string endname = string.Empty;
536 readState = ReadState.Interactive;
538 while (startname != endname) {
542 saveToXmlBuffer = false;
543 string OuterXml = xmlBuffer.ToString ();
544 xmlBuffer.Length = 0;
550 public override string ReadString ()
552 throw new NotImplementedException ();
556 public void ResetState ()
558 throw new NotImplementedException ();
561 public override void ResolveEntity ()
563 // XmlTextReaders don't resolve entities.
564 throw new InvalidOperationException ("XmlTextReaders don't resolve entities.");
571 private XmlParserContext parserContext;
573 private TextReader reader;
574 private ReadState readState;
577 private bool depthDown;
579 private bool popScope;
581 private XmlNodeType nodeType;
583 private string prefix;
584 private string localName;
585 private string namespaceURI;
586 private bool isEmptyElement;
587 private string value;
589 private XmlNodeType saveNodeType;
590 private string saveName;
591 private string savePrefix;
592 private string saveLocalName;
593 private string saveNamespaceURI;
594 private bool saveIsEmptyElement;
596 private Hashtable attributes;
597 private ArrayList orderedAttributes;
598 private IEnumerator orderedAttributesEnumerator;
600 private bool returnEntityReference;
601 private string entityReferenceName;
603 private char [] nameBuffer;
604 private int nameLength;
605 private int nameCapacity;
606 private const int initialNameCapacity = 256;
608 private char [] valueBuffer;
609 private int valueLength;
610 private int valueCapacity;
611 private const int initialValueCapacity = 8192;
613 private StringBuilder xmlBuffer; // This is for Read(Inner|Outer)Xml
614 private StringBuilder currentTag; // A buffer for ReadContent for ReadOuterXml
615 private bool saveToXmlBuffer;
619 readState = ReadState.Initial;
626 nodeType = XmlNodeType.None;
628 prefix = String.Empty;
629 localName = string.Empty;
630 isEmptyElement = false;
631 value = String.Empty;
633 attributes = new Hashtable ();
634 orderedAttributes = new ArrayList ();
635 orderedAttributesEnumerator = null;
637 returnEntityReference = false;
638 entityReferenceName = String.Empty;
640 nameBuffer = new char [initialNameCapacity];
642 nameCapacity = initialNameCapacity;
644 valueBuffer = new char [initialValueCapacity];
646 valueCapacity = initialValueCapacity;
648 xmlBuffer = new StringBuilder ();
649 currentTag = new StringBuilder ();
652 // Use this method rather than setting the properties
653 // directly so that all the necessary properties can
654 // be changed in harmony with each other. Maybe the
655 // fields should be in a seperate class to help enforce
657 private void SetProperties (
658 XmlNodeType nodeType,
662 bool clearAttributes)
664 this.nodeType = nodeType;
666 this.isEmptyElement = isEmptyElement;
672 int indexOfColon = name.IndexOf (':');
674 if (indexOfColon == -1) {
675 prefix = String.Empty;
678 prefix = name.Substring (0, indexOfColon);
679 localName = name.Substring (indexOfColon + 1);
682 namespaceURI = LookupNamespace (prefix);
685 private void SaveProperties ()
687 saveNodeType = nodeType;
690 saveLocalName = localName;
691 saveNamespaceURI = namespaceURI;
692 saveIsEmptyElement = isEmptyElement;
693 // An element's value is always String.Empty.
696 private void RestoreProperties ()
698 nodeType = saveNodeType;
701 localName = saveLocalName;
702 namespaceURI = saveNamespaceURI;
703 isEmptyElement = saveIsEmptyElement;
704 value = String.Empty;
707 private void AddAttribute (string name, string value)
709 attributes.Add (name, value);
710 orderedAttributes.Add (name);
713 private void ClearAttributes ()
715 if (attributes.Count > 0) {
717 orderedAttributes.Clear ();
720 orderedAttributesEnumerator = null;
723 private int PeekChar ()
725 return reader.Peek ();
728 private int ReadChar ()
730 int ch = reader.Read ();
731 if (saveToXmlBuffer) {
732 xmlBuffer.Append ((char) ch);
734 currentTag.Append ((char) ch);
738 // This should really keep track of some state so
739 // that it's not possible to have more than one document
740 // element or text outside of the document element.
741 private bool ReadContent ()
744 currentTag.Length = 0;
746 parserContext.NamespaceManager.PopScope ();
753 if (returnEntityReference) {
755 SetEntityReferenceProperties ();
766 readState = ReadState.EndOfFile;
768 XmlNodeType.None, // nodeType
769 String.Empty, // name
770 false, // isEmptyElement
771 String.Empty, // value
772 true // clearAttributes
786 private void SetEntityReferenceProperties ()
789 XmlNodeType.EntityReference, // nodeType
790 entityReferenceName, // name
791 false, // isEmptyElement
792 String.Empty, // value
793 true // clearAttributes
796 returnEntityReference = false;
797 entityReferenceName = String.Empty;
800 // The leading '<' has already been consumed.
801 private void ReadTag ()
811 ReadProcessingInstruction ();
823 // The leading '<' has already been consumed.
824 private void ReadStartTag ()
826 parserContext.NamespaceManager.PushScope ();
828 string name = ReadName ();
831 bool isEmptyElement = false;
835 if (XmlChar.IsFirstNameChar (PeekChar ()))
838 if (PeekChar () == '/') {
840 isEmptyElement = true;
850 XmlNodeType.Element, // nodeType
852 isEmptyElement, // isEmptyElement
853 String.Empty, // value
854 false // clearAttributes
858 // The reader is positioned on the first character
859 // of the element's name.
860 private void ReadEndTag ()
862 string name = ReadName ();
869 XmlNodeType.EndElement, // nodeType
871 false, // isEmptyElement
872 String.Empty, // value
873 true // clearAttributes
879 private void AppendNameChar (int ch)
881 CheckNameCapacity ();
882 nameBuffer [nameLength++] = (char)ch;
885 private void CheckNameCapacity ()
887 if (nameLength == nameCapacity) {
888 nameCapacity = nameCapacity * 2;
889 char [] oldNameBuffer = nameBuffer;
890 nameBuffer = new char [nameCapacity];
891 Array.Copy (oldNameBuffer, nameBuffer, nameLength);
895 private string CreateNameString ()
897 return new String (nameBuffer, 0, nameLength);
900 private void AppendValueChar (int ch)
902 CheckValueCapacity ();
903 valueBuffer [valueLength++] = (char)ch;
906 private void CheckValueCapacity ()
908 if (valueLength == valueCapacity) {
909 valueCapacity = valueCapacity * 2;
910 char [] oldValueBuffer = valueBuffer;
911 valueBuffer = new char [valueCapacity];
912 Array.Copy (oldValueBuffer, valueBuffer, valueLength);
916 private string CreateValueString ()
918 return new String (valueBuffer, 0, valueLength);
921 // The reader is positioned on the first character
923 private void ReadText ()
927 int ch = PeekChar ();
929 while (ch != '<' && ch != -1) {
932 if (ReadReference (false))
935 AppendValueChar (ReadChar ());
940 if (returnEntityReference && valueLength == 0) {
942 SetEntityReferenceProperties ();
950 XmlNodeType.Text, // nodeType
951 String.Empty, // name
952 false, // isEmptyElement
953 CreateValueString (), // value
954 true // clearAttributes
959 // The leading '&' has already been consumed.
960 // Returns true if the entity reference isn't a simple
961 // character reference or one of the predefined entities.
962 // This allows the ReadText method to break so that the
963 // next call to Read will return the EntityReference node.
964 private bool ReadReference (bool ignoreEntityReferences)
966 if (PeekChar () == '#') {
968 ReadCharacterReference ();
970 ReadEntityReference (ignoreEntityReferences);
972 return returnEntityReference;
975 private void ReadCharacterReference ()
979 if (PeekChar () == 'x') {
982 while (PeekChar () != ';' && PeekChar () != -1) {
983 int ch = ReadChar ();
985 if (ch >= '0' && ch <= '9')
986 value = (value << 4) + ch - '0';
987 else if (ch >= 'A' && ch <= 'F')
988 value = (value << 4) + ch - 'A' + 10;
989 else if (ch >= 'a' && ch <= 'f')
990 value = (value << 4) + ch - 'a' + 10;
992 throw new XmlException (
994 "invalid hexadecimal digit: {0} (#x{1:X})",
999 while (PeekChar () != ';' && PeekChar () != -1) {
1000 int ch = ReadChar ();
1002 if (ch >= '0' && ch <= '9')
1003 value = value * 10 + ch - '0';
1005 throw new XmlException (
1007 "invalid decimal digit: {0} (#x{1:X})",
1015 AppendValueChar (value);
1018 private void ReadEntityReference (bool ignoreEntityReferences)
1022 int ch = PeekChar ();
1024 while (ch != ';' && ch != -1) {
1025 AppendNameChar (ReadChar ());
1031 string name = CreateNameString ();
1036 AppendValueChar ('<');
1039 AppendValueChar ('>');
1042 AppendValueChar ('&');
1045 AppendValueChar ('\'');
1048 AppendValueChar ('"');
1051 if (ignoreEntityReferences) {
1052 AppendValueChar ('&');
1054 foreach (char ch2 in name) {
1055 AppendValueChar (ch2);
1058 AppendValueChar (';');
1060 returnEntityReference = true;
1061 entityReferenceName = name;
1067 // The reader is positioned on the first character of
1068 // the attribute name.
1069 private void ReadAttributes ()
1072 string name = ReadName ();
1076 string value = ReadAttribute ();
1079 if (name == "xmlns")
1080 parserContext.NamespaceManager.AddNamespace (String.Empty, value);
1081 else if (name.StartsWith ("xmlns:"))
1082 parserContext.NamespaceManager.AddNamespace (name.Substring (6), value);
1084 AddAttribute (name, value);
1085 } while (PeekChar () != '/' && PeekChar () != '>' && PeekChar () != -1);
1088 // The reader is positioned on the quote character.
1089 private string ReadAttribute ()
1091 int quoteChar = ReadChar ();
1093 if (quoteChar != '\'' && quoteChar != '\"')
1094 throw new XmlException ("an attribute value was not quoted");
1098 while (PeekChar () != quoteChar) {
1099 int ch = ReadChar ();
1104 throw new XmlException ("attribute values cannot contain '<'");
1106 ReadReference (true);
1109 throw new XmlException ("unexpected end of file in an attribute value");
1111 AppendValueChar (ch);
1116 ReadChar (); // quoteChar
1118 return CreateValueString ();
1121 // The reader is positioned on the first character
1123 private void ReadProcessingInstruction ()
1125 string target = ReadName ();
1130 while (PeekChar () != -1) {
1131 int ch = ReadChar ();
1133 if (ch == '?' && PeekChar () == '>') {
1138 AppendValueChar ((char)ch);
1142 XmlNodeType.ProcessingInstruction, // nodeType
1144 false, // isEmptyElement
1145 CreateValueString (), // value
1146 true // clearAttributes
1150 // The reader is positioned on the first character after
1151 // the leading '<!'.
1152 private void ReadDeclaration ()
1154 int ch = PeekChar ();
1176 // The reader is positioned on the first character after
1177 // the leading '<!--'.
1178 private void ReadComment ()
1182 while (PeekChar () != -1) {
1183 int ch = ReadChar ();
1185 if (ch == '-' && PeekChar () == '-') {
1188 if (PeekChar () != '>')
1189 throw new XmlException ("comments cannot contain '--'");
1195 AppendValueChar ((char)ch);
1199 XmlNodeType.Comment, // nodeType
1200 String.Empty, // name
1201 false, // isEmptyElement
1202 CreateValueString (), // value
1203 true // clearAttributes
1207 // The reader is positioned on the first character after
1208 // the leading '<![CDATA['.
1209 private void ReadCDATA ()
1213 while (PeekChar () != -1) {
1214 int ch = ReadChar ();
1216 if (ch == ']' && PeekChar () == ']') {
1217 ch = ReadChar (); // ']'
1219 if (PeekChar () == '>') {
1223 AppendValueChar (']');
1224 AppendValueChar (']');
1229 AppendValueChar ((char)ch);
1235 XmlNodeType.CDATA, // nodeType
1236 String.Empty, // name
1237 false, // isEmptyElement
1238 CreateValueString (), // value
1239 true // clearAttributes
1243 // The reader is positioned on the first character
1245 private string ReadName ()
1247 if (!XmlChar.IsFirstNameChar (PeekChar ()))
1248 throw new XmlException ("a name did not start with a legal character");
1252 AppendNameChar (ReadChar ());
1254 while (XmlChar.IsNameChar (PeekChar ())) {
1255 AppendNameChar (ReadChar ());
1258 return CreateNameString ();
1261 // Read the next character and compare it against the
1262 // specified character.
1263 private void Expect (int expected)
1265 int ch = ReadChar ();
1267 if (ch != expected) {
1268 throw new XmlException (
1270 "expected '{0}' ({1:X}) but found '{2}' ({3:X})",
1278 // Does not consume the first non-whitespace character.
1279 private void SkipWhitespace ()
1281 while (XmlChar.IsWhitespace (PeekChar ()))