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 // ParserContext and NameTables aren't being used yet.
21 // Some thought needs to be given to performance. There's too many
22 // strings being allocated.
24 // None of the MoveTo methods have 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)
68 protected XmlTextReader (XmlNameTable nt)
70 throw new NotImplementedException ();
74 public XmlTextReader (Stream input, XmlNameTable nt)
76 throw new NotImplementedException ();
80 public XmlTextReader (string url, Stream input)
82 throw new NotImplementedException ();
86 public XmlTextReader (string url, TextReader input)
88 throw new NotImplementedException ();
92 public XmlTextReader (string url, XmlNameTable nt)
94 throw new NotImplementedException ();
98 public XmlTextReader (TextReader input, XmlNameTable nt)
100 throw new NotImplementedException ();
104 public XmlTextReader (Stream xmlFragment, XmlNodeType fragType, XmlParserContext context)
106 throw new NotImplementedException ();
110 public XmlTextReader (string url, Stream input, XmlNameTable nt)
112 throw new NotImplementedException ();
116 public XmlTextReader (string url, TextReader input, XmlNameTable nt)
118 throw new NotImplementedException ();
122 public XmlTextReader (string xmlFragment, XmlNodeType fragType, XmlParserContext context)
124 throw new NotImplementedException ();
131 public override int AttributeCount
133 get { return attributes.Count; }
137 public override string BaseURI
139 get { throw new NotImplementedException (); }
142 public override int Depth
144 get { return depth > 0 ? depth : 0; }
148 public Encoding Encoding
150 get { throw new NotImplementedException (); }
153 public override bool EOF
158 readState == ReadState.EndOfFile ||
159 readState == ReadState.Closed;
163 public override bool HasValue
165 get { return value != String.Empty; }
168 public override bool IsDefault
172 // XmlTextReader does not expand default attributes.
177 public override bool IsEmptyElement
179 get { return isEmptyElement; }
182 public override string this [int i]
184 get { return GetAttribute (i); }
187 public override string this [string name]
189 get { return GetAttribute (name); }
192 public override string this [string localName, string namespaceName]
194 get { return GetAttribute (localName, namespaceName); }
198 public int LineNumber
200 get { throw new NotImplementedException (); }
204 public int LinePosition
206 get { throw new NotImplementedException (); }
209 public override string LocalName
211 get { return localName; }
214 public override string Name
220 public bool Namespaces
222 get { throw new NotImplementedException (); }
223 set { throw new NotImplementedException (); }
226 public override string NamespaceURI
228 get { return namespaceURI; }
231 public override XmlNameTable NameTable
233 get { return nameTable; }
236 public override XmlNodeType NodeType
238 get { return nodeType; }
242 public bool Normalization
244 get { throw new NotImplementedException (); }
245 set { throw new NotImplementedException (); }
248 public override string Prefix
250 get { return prefix; }
254 public override char QuoteChar
256 get { throw new NotImplementedException (); }
259 public override ReadState ReadState
261 get { return readState; }
264 public override string Value
266 get { return value; }
270 public WhitespaceHandling WhitespaceHandling
272 get { throw new NotImplementedException (); }
273 set { throw new NotImplementedException (); }
277 public override string XmlLang
279 get { throw new NotImplementedException (); }
283 public XmlResolver XmlResolver
285 set { throw new NotImplementedException (); }
289 public override XmlSpace XmlSpace
291 get { throw new NotImplementedException (); }
299 public override void Close ()
301 readState = ReadState.Closed;
305 public override string GetAttribute (int i)
307 throw new NotImplementedException ();
310 public override string GetAttribute (string name)
312 return attributes [name] as string;
315 public override string GetAttribute (string localName, string namespaceURI)
317 foreach (DictionaryEntry entry in attributes)
319 string thisName = entry.Key as string;
321 int indexOfColon = thisName.IndexOf (':');
323 if (indexOfColon != -1) {
324 string thisLocalName = thisName.Substring (indexOfColon + 1);
326 if (localName == thisLocalName) {
327 string thisPrefix = thisName.Substring (0, indexOfColon);
328 string thisNamespaceURI = LookupNamespace (thisPrefix);
330 if (namespaceURI == thisNamespaceURI)
331 return attributes [thisName] as string;
333 } else if (localName == "xmlns" && namespaceURI == "http://www.w3.org/2000/xmlns/" && thisName == "xmlns")
334 return attributes[thisName] as string;
341 public TextReader GetRemainder ()
343 throw new NotImplementedException ();
347 bool IXmlLineInfo.HasLineInfo ()
349 throw new NotImplementedException ();
352 public override string LookupNamespace (string prefix)
354 return namespaceManager.LookupNamespace (prefix);
358 public override void MoveToAttribute (int i)
360 throw new NotImplementedException ();
364 public override bool MoveToAttribute (string name)
366 throw new NotImplementedException ();
370 public override bool MoveToAttribute (string localName, string namespaceName)
372 throw new NotImplementedException ();
375 public override bool MoveToElement ()
377 if (nodeType == XmlNodeType.Attribute) {
378 RestoreProperties ();
385 public override bool MoveToFirstAttribute ()
388 return MoveToNextAttribute ();
391 public override bool MoveToNextAttribute ()
393 if (attributes == null)
396 if (attributeEnumerator == null) {
398 attributeEnumerator = attributes.GetEnumerator();
401 if (attributeEnumerator.MoveNext ()) {
402 string name = attributeEnumerator.Key as string;
403 string value = attributeEnumerator.Value as string;
405 XmlNodeType.Attribute, // nodeType
407 false, // isEmptyElement
409 false // clearAttributes
417 public override bool Read ()
421 readState = ReadState.Interactive;
423 more = ReadContent ();
429 public override bool ReadAttributeValue ()
431 throw new NotImplementedException ();
435 public int ReadBase64 (byte[] buffer, int offset, int length)
437 throw new NotImplementedException ();
441 public int ReadBinHex (byte[] buffer, int offset, int length)
443 throw new NotImplementedException ();
447 public int ReadChars (char[] buffer, int offset, int length)
449 throw new NotImplementedException ();
453 public override string ReadInnerXml ()
455 throw new NotImplementedException ();
459 public override string ReadOuterXml ()
461 throw new NotImplementedException ();
465 public override string ReadString ()
467 throw new NotImplementedException ();
471 public void ResetState ()
473 throw new NotImplementedException ();
476 public override void ResolveEntity ()
478 // XmlTextReaders don't resolve entities.
479 throw new InvalidOperationException ("XmlTextReaders don't resolve entities.");
486 private TextReader reader;
487 private ReadState readState;
490 private bool depthDown;
492 private XmlNameTable nameTable;
493 private XmlNamespaceManager namespaceManager;
494 private bool popScope;
496 private XmlNodeType nodeType;
498 private string prefix;
499 private string localName;
500 private string namespaceURI;
501 private bool isEmptyElement;
502 private string value;
504 private XmlNodeType saveNodeType;
505 private string saveName;
506 private string savePrefix;
507 private string saveLocalName;
508 private string saveNamespaceURI;
509 private bool saveIsEmptyElement;
511 private Hashtable attributes;
512 private IDictionaryEnumerator attributeEnumerator;
514 private bool returnEntityReference;
515 private string entityReferenceName;
517 private char[] nameBuffer;
518 private int nameLength;
519 private int nameCapacity;
520 private const int initialNameCapacity = 256;
522 private char[] valueBuffer;
523 private int valueLength;
524 private int valueCapacity;
525 private const int initialValueCapacity = 8192;
529 if (nameTable == null)
530 nameTable = new NameTable ();
532 namespaceManager = new XmlNamespaceManager (nameTable);
535 readState = ReadState.Initial;
540 nodeType = XmlNodeType.None;
542 prefix = String.Empty;
543 localName = string.Empty;
544 isEmptyElement = false;
545 value = String.Empty;
547 attributes = new Hashtable ();
548 attributeEnumerator = null;
550 returnEntityReference = false;
551 entityReferenceName = String.Empty;
553 nameBuffer = new char[initialNameCapacity];
555 nameCapacity = initialNameCapacity;
557 valueBuffer = new char[initialValueCapacity];
559 valueCapacity = initialValueCapacity;
562 // Use this method rather than setting the properties
563 // directly so that all the necessary properties can
564 // be changed in harmony with each other. Maybe the
565 // fields should be in a seperate class to help enforce
567 private void SetProperties (
568 XmlNodeType nodeType,
572 bool clearAttributes)
574 this.nodeType = nodeType;
576 this.isEmptyElement = isEmptyElement;
582 int indexOfColon = name.IndexOf (':');
584 if (indexOfColon == -1) {
585 prefix = String.Empty;
588 prefix = name.Substring (0, indexOfColon);
589 localName = name.Substring (indexOfColon + 1);
592 namespaceURI = LookupNamespace (prefix);
595 private void SaveProperties ()
597 saveNodeType = nodeType;
600 saveLocalName = localName;
601 saveNamespaceURI = namespaceURI;
602 saveIsEmptyElement = isEmptyElement;
603 // An element's value is always String.Empty.
606 private void RestoreProperties ()
608 nodeType = saveNodeType;
611 localName = saveLocalName;
612 namespaceURI = saveNamespaceURI;
613 isEmptyElement = saveIsEmptyElement;
614 value = String.Empty;
617 private void AddAttribute (string name, string value)
619 attributes.Add (name, value);
622 private void ClearAttributes ()
624 if (attributes.Count > 0)
627 attributeEnumerator = null;
630 private int PeekChar ()
632 return reader.Peek ();
635 private int ReadChar ()
637 return reader.Read ();
640 // This should really keep track of some state so
641 // that it's not possible to have more than one document
642 // element or text outside of the document element.
643 private bool ReadContent ()
648 namespaceManager.PopScope ();
655 if (returnEntityReference) {
657 SetEntityReferenceProperties ();
668 readState = ReadState.EndOfFile;
670 XmlNodeType.None, // nodeType
671 String.Empty, // name
672 false, // isEmptyElement
673 String.Empty, // value
674 true // clearAttributes
688 private void SetEntityReferenceProperties ()
691 XmlNodeType.EntityReference, // nodeType
692 entityReferenceName, // name
693 false, // isEmptyElement
694 String.Empty, // value
695 true // clearAttributes
698 returnEntityReference = false;
699 entityReferenceName = String.Empty;
702 // The leading '<' has already been consumed.
703 private void ReadTag ()
713 ReadProcessingInstruction ();
725 // The leading '<' has already been consumed.
726 private void ReadStartTag ()
728 namespaceManager.PushScope ();
730 string name = ReadName ();
733 bool isEmptyElement = false;
737 if (XmlChar.IsFirstNameChar (PeekChar ()))
740 if (PeekChar () == '/') {
742 isEmptyElement = true;
752 XmlNodeType.Element, // nodeType
754 isEmptyElement, // isEmptyElement
755 String.Empty, // value
756 false // clearAttributes
760 // The reader is positioned on the first character
761 // of the element's name.
762 private void ReadEndTag ()
764 string name = ReadName ();
771 XmlNodeType.EndElement, // nodeType
773 false, // isEmptyElement
774 String.Empty, // value
775 true // clearAttributes
781 private void AppendNameChar (int ch)
783 CheckNameCapacity ();
784 nameBuffer[nameLength++] = (char)ch;
787 private void CheckNameCapacity ()
789 if (nameLength == nameCapacity) {
790 nameCapacity = nameCapacity * 2;
791 char[] oldNameBuffer = nameBuffer;
792 nameBuffer = new char[nameCapacity];
793 Array.Copy (oldNameBuffer, nameBuffer, nameLength);
797 private string CreateNameString ()
799 return new String (nameBuffer, 0, nameLength);
802 private void AppendValueChar (int ch)
804 CheckValueCapacity ();
805 valueBuffer[valueLength++] = (char)ch;
808 private void CheckValueCapacity ()
810 if (valueLength == valueCapacity) {
811 valueCapacity = valueCapacity * 2;
812 char[] oldValueBuffer = valueBuffer;
813 valueBuffer = new char[valueCapacity];
814 Array.Copy (oldValueBuffer, valueBuffer, valueLength);
818 private string CreateValueString ()
820 return new String (valueBuffer, 0, valueLength);
823 // The reader is positioned on the first character
825 private void ReadText ()
829 int ch = PeekChar ();
831 while (ch != '<' && ch != -1) {
834 if (ReadReference (false))
837 AppendValueChar (ReadChar ());
842 if (returnEntityReference && valueLength == 0) {
844 SetEntityReferenceProperties ();
852 XmlNodeType.Text, // nodeType
853 String.Empty, // name
854 false, // isEmptyElement
855 CreateValueString (), // value
856 true // clearAttributes
861 // The leading '&' has already been consumed.
862 // Returns true if the entity reference isn't a simple
863 // character reference or one of the predefined entities.
864 // This allows the ReadText method to break so that the
865 // next call to Read will return the EntityReference node.
866 private bool ReadReference (bool ignoreEntityReferences)
868 if (PeekChar () == '#') {
870 ReadCharacterReference ();
872 ReadEntityReference (ignoreEntityReferences);
874 return returnEntityReference;
877 private void ReadCharacterReference ()
881 if (PeekChar () == 'x') {
884 while (PeekChar () != ';' && PeekChar () != -1) {
885 int ch = ReadChar ();
887 if (ch >= '0' && ch <= '9')
888 value = (value << 4) + ch - '0';
889 else if (ch >= 'A' && ch <= 'F')
890 value = (value << 4) + ch - 'A' + 10;
891 else if (ch >= 'a' && ch <= 'f')
892 value = (value << 4) + ch - 'a' + 10;
894 throw new XmlException (
896 "invalid hexadecimal digit: {0} (#x{1:X})",
901 while (PeekChar () != ';' && PeekChar () != -1) {
902 int ch = ReadChar ();
904 if (ch >= '0' && ch <= '9')
905 value = value * 10 + ch - '0';
907 throw new XmlException (
909 "invalid decimal digit: {0} (#x{1:X})",
917 AppendValueChar (value);
920 private void ReadEntityReference (bool ignoreEntityReferences)
924 int ch = PeekChar ();
926 while (ch != ';' && ch != -1) {
927 AppendNameChar (ReadChar ());
933 string name = CreateNameString ();
938 AppendValueChar ('<');
941 AppendValueChar ('>');
944 AppendValueChar ('&');
947 AppendValueChar ('\'');
950 AppendValueChar ('"');
953 if (ignoreEntityReferences) {
954 AppendValueChar ('&');
956 foreach (char ch2 in name) {
957 AppendValueChar (ch2);
960 AppendValueChar (';');
962 returnEntityReference = true;
963 entityReferenceName = name;
969 // The reader is positioned on the first character of
970 // the attribute name.
971 private void ReadAttributes ()
974 string name = ReadName ();
978 string value = ReadAttribute ();
982 namespaceManager.AddNamespace (String.Empty, value);
983 else if (name.StartsWith ("xmlns:"))
984 namespaceManager.AddNamespace (name.Substring (6), value);
986 AddAttribute (name, value);
987 } while (PeekChar () != '/' && PeekChar () != '>' && PeekChar () != -1);
990 // The reader is positioned on the quote character.
991 private string ReadAttribute ()
993 int quoteChar = ReadChar ();
995 if (quoteChar != '\'' && quoteChar != '\"')
996 throw new XmlException ("an attribute value was not quoted");
1000 while (PeekChar () != quoteChar) {
1001 int ch = ReadChar ();
1006 throw new XmlException ("attribute values cannot contain '<'");
1008 ReadReference (true);
1011 throw new XmlException ("unexpected end of file in an attribute value");
1013 AppendValueChar (ch);
1018 ReadChar (); // quoteChar
1020 return CreateValueString ();
1023 // The reader is positioned on the first character
1025 private void ReadProcessingInstruction ()
1027 string target = ReadName ();
1032 while (PeekChar () != -1) {
1033 int ch = ReadChar ();
1035 if (ch == '?' && PeekChar () == '>') {
1040 AppendValueChar ((char)ch);
1044 XmlNodeType.ProcessingInstruction, // nodeType
1046 false, // isEmptyElement
1047 CreateValueString (), // value
1048 true // clearAttributes
1052 // The reader is positioned on the first character after
1053 // the leading '<!'.
1054 private void ReadDeclaration ()
1056 int ch = PeekChar ();
1078 // The reader is positioned on the first character after
1079 // the leading '<!--'.
1080 private void ReadComment ()
1084 while (PeekChar () != -1) {
1085 int ch = ReadChar ();
1087 if (ch == '-' && PeekChar () == '-') {
1090 if (PeekChar () != '>')
1091 throw new XmlException ("comments cannot contain '--'");
1097 AppendValueChar ((char)ch);
1101 XmlNodeType.Comment, // nodeType
1102 String.Empty, // name
1103 false, // isEmptyElement
1104 CreateValueString (), // value
1105 true // clearAttributes
1109 // The reader is positioned on the first character after
1110 // the leading '<![CDATA['.
1111 private void ReadCDATA ()
1115 while (PeekChar () != -1) {
1116 int ch = ReadChar ();
1118 if (ch == ']' && PeekChar () == ']') {
1119 ch = ReadChar (); // ']'
1121 if (PeekChar () == '>') {
1125 AppendValueChar (']');
1126 AppendValueChar (']');
1131 AppendValueChar ((char)ch);
1137 XmlNodeType.CDATA, // nodeType
1138 String.Empty, // name
1139 false, // isEmptyElement
1140 CreateValueString (), // value
1141 true // clearAttributes
1145 // The reader is positioned on the first character
1147 private string ReadName ()
1149 if (!XmlChar.IsFirstNameChar (PeekChar ()))
1150 throw new XmlException ("a name did not start with a legal character");
1154 AppendNameChar (ReadChar ());
1156 while (XmlChar.IsNameChar (PeekChar ())) {
1157 AppendNameChar (ReadChar ());
1160 return CreateNameString ();
1163 // Read the next character and compare it against the
1164 // specified character.
1165 private void Expect (int expected)
1167 int ch = ReadChar ();
1169 if (ch != expected) {
1170 throw new XmlException (
1172 "expected '{0}' ({1:X}) but found '{2}' ({3:X})",
1180 // Does not consume the first non-whitespace character.
1181 private void SkipWhitespace ()
1183 while (XmlChar.IsWhitespace (PeekChar ()))