X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FSystem.XML%2FSystem.Xml%2FXmlTextReader.cs;h=5895b4b6572c36603d6de2a8d8f9c304b8dd7903;hb=ea63c7d9e2a5f5808af6679a0ee2ae1af124a77a;hp=e385ba613bc257e0fc6a708e918f59852404cc38;hpb=71434b89d00bbf36c9b1a6b96b55790a50b764c2;p=mono.git diff --git a/mcs/class/System.XML/System.Xml/XmlTextReader.cs b/mcs/class/System.XML/System.Xml/XmlTextReader.cs index e385ba613bc..5895b4b6572 100644 --- a/mcs/class/System.XML/System.Xml/XmlTextReader.cs +++ b/mcs/class/System.XML/System.Xml/XmlTextReader.cs @@ -1,11 +1,11 @@ -// -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- // -// System.Xml.XmlTextReader.cs +// System.Xml.XmlTextReader // // Author: // Jason Diamond (jason@injektilo.org) +// Adam Treat (manyoso@yahoo.com) // -// (C) 2001 Jason Diamond http://injektilo.org/ +// (C) 2001, 2002 Jason Diamond http://injektilo.org/ // // FIXME: @@ -13,186 +13,140 @@ // instructions, and comments are OK. // // It barfs on DOCTYPE declarations. +// => No barfing, but parsing is incomplete. +// DTD nodes are not still created. // // There's also no checking being done for either well-formedness // or validity. // -// ParserContext and NameTables aren't being used yet. +// NameTables aren't being used everywhere yet. // // Some thought needs to be given to performance. There's too many // strings being allocated. // -// None of the MoveTo methods have been implemented yet. +// Some of the MoveTo methods haven't been implemented yet. // // LineNumber and LinePosition aren't being tracked. // // xml:space, xml:lang, and xml:base aren't being tracked. // -// Depth isn't being tracked. using System; using System.Collections; using System.IO; -using System.Net; using System.Text; namespace System.Xml { public class XmlTextReader : XmlReader, IXmlLineInfo { - // constructors + WhitespaceHandling whitespaceHandling = WhitespaceHandling.All; + #region Constructors - protected XmlTextReader() + protected XmlTextReader () { - Init(); } - public XmlTextReader(Stream input) + public XmlTextReader (Stream input) + : this (new StreamReader (input)) { - Init(); - reader = new StreamReader( - input, - Encoding.UTF8, - true); } - public XmlTextReader(string url) + public XmlTextReader (string url) + : this(url, new NameTable ()) { - Init(); - WebClient client = new WebClient(); - reader = new StreamReader( - client.OpenRead(url), - Encoding.UTF8, - true); } - public XmlTextReader(TextReader input) + public XmlTextReader (TextReader input) + : this (input, new NameTable ()) { - Init(); - reader = input; } - public XmlTextReader(Stream input, XmlNameTable nameTable) - { - this.nameTable = nameTable; + protected XmlTextReader (XmlNameTable nt) + : this (String.Empty, null, XmlNodeType.None, null) + { + } - // TODO: implement me. - throw new NotImplementedException(); + public XmlTextReader (Stream input, XmlNameTable nt) + : this(new StreamReader (input), nt) + { } - public XmlTextReader(string baseURI, Stream input) + public XmlTextReader (string url, Stream input) + : this (url, new StreamReader (input)) { - // TODO: implement me. - throw new NotImplementedException(); } - public XmlTextReader(string baseURI, TextReader input) + public XmlTextReader (string url, TextReader input) + : this (url, input, new NameTable ()) { - // TODO: implement me. - throw new NotImplementedException(); } - public XmlTextReader(string url, XmlNameTable nameTable) + [MonoTODO("Non-filename-url must be supported. Waiting for WebClient")] + public XmlTextReader (string url, XmlNameTable nt) + // : this(url, new StreamReader ((Stream)new XmlUrlResolver ().GetEntity (new Uri (url), null, typeof(Stream))), nt) + : this (url, new StreamReader (url), nt) { - this.nameTable = nameTable; - - // TODO: implement me. - throw new NotImplementedException(); } - public XmlTextReader( - TextReader input, - XmlNameTable nameTable) + public XmlTextReader (TextReader input, XmlNameTable nt) + : this(String.Empty, input, nt) { - this.nameTable = nameTable; - - // TODO: implement me. - throw new NotImplementedException(); } - public XmlTextReader( - Stream inputFragment, - XmlNodeType fragmentType, - XmlParserContext context) + public XmlTextReader (Stream xmlFragment, XmlNodeType fragType, XmlParserContext context) + : this (String.Empty, new StreamReader (xmlFragment), fragType, context) { - // TODO: implement me. - throw new NotImplementedException(); } - public XmlTextReader( - string baseURI, - Stream input, - XmlNameTable nameTable) + public XmlTextReader (string url, Stream input, XmlNameTable nt) + : this (url, new StreamReader (input), nt) { - this.nameTable = nameTable; + } - // TODO: implement me. - throw new NotImplementedException(); + public XmlTextReader (string url, TextReader input, XmlNameTable nt) + : this (url, input, XmlNodeType.Document, new XmlParserContext (nt, new XmlNamespaceManager (nt), null, XmlSpace.None)) + { } - public XmlTextReader( - string baseURI, - TextReader input, - XmlNameTable nameTable) + [MonoTODO("TODO as same as private XmlTextReader(TextReader, XmlNodeType, XmlParserContext)")] + public XmlTextReader (string xmlFragment, XmlNodeType fragType, XmlParserContext context) + : this (String.Empty, new StringReader (xmlFragment), fragType, context) { - this.nameTable = nameTable; - // TODO: implement me. - throw new NotImplementedException(); } - public XmlTextReader( - string fragment, - XmlNodeType fragmentType, - XmlParserContext context) + // TODO still remains as described at head of this file, + // but it might not be TODO of the constructors... + XmlTextReader (string url, TextReader fragment, XmlNodeType fragType, XmlParserContext context) { - // TODO: implement me. - throw new NotImplementedException(); + this.SetReaderContext(url, context); + this.SetReaderFragment(fragment, fragType); } - // properties + #endregion + + #region Properties public override int AttributeCount { - get - { - return attributes.Count; - } + get { return attributes.Count; } } public override string BaseURI { - get - { - // TODO: implement me. - return null; - } - } - - public override bool CanResolveEntity - { - get - { - // XmlTextReaders don't resolve entities. - return false; - } + get { return parserContext.BaseURI; } } public override int Depth { - get - { - // TODO: implement me. - return depth > 0 ? depth : 0; + get { + return elementDepth; } } public Encoding Encoding { - get - { - // TODO: implement me. - return null; - } + get { return parserContext.Encoding; } } public override bool EOF @@ -207,10 +161,7 @@ namespace System.Xml public override bool HasValue { - get - { - return value != String.Empty; - } + get { return value != String.Empty; } } public override bool IsDefault @@ -224,370 +175,499 @@ namespace System.Xml public override bool IsEmptyElement { - get - { - return isEmptyElement; - } + get { return isEmptyElement; } } - public override string this[int i] + public override string this [int i] { - get - { - return GetAttribute(i); - } + get { return GetAttribute (i); } } - public override string this[string name] + public override string this [string name] { - get - { - return GetAttribute(name); - } + get { return GetAttribute (name); } } - public override string this[ - string localName, - string namespaceName] + public override string this [string localName, string namespaceName] { - get - { - return GetAttribute(localName, namespaceName); - } + get { return GetAttribute (localName, namespaceName); } } public int LineNumber { - get - { - // TODO: implement me. - return 0; - } + get { return line; } } public int LinePosition { - get - { - // TODO: implement me. - return 0; - } + get { return column; } } public override string LocalName { - get - { - return localName; - } + get { return localName; } } public override string Name { - get - { - return name; - } + get { return name; } } + [MonoTODO] public bool Namespaces { - get - { - // TODO: implement me. - return false; - } - - set - { - // TODO: implement me. - } + get { throw new NotImplementedException (); } + set { throw new NotImplementedException (); } } public override string NamespaceURI { - get - { - return namespaceURI; - } + get { return namespaceURI; } } public override XmlNameTable NameTable { - get - { - // TODO: implement me. - return null; - } + get { return parserContext.NameTable; } } public override XmlNodeType NodeType { - get - { - return nodeType; - } + get { return nodeType; } } + [MonoTODO] public bool Normalization { - get - { - // TODO: implement me. - return false; - } - - set - { - // TODO: implement me. - } + get { throw new NotImplementedException (); } + set { throw new NotImplementedException (); } } public override string Prefix { - get - { - return prefix; - } + get { return prefix; } } + [MonoTODO] public override char QuoteChar { - get - { - // TODO: implement me. - return '"'; - } + get { throw new NotImplementedException (); } } public override ReadState ReadState { - get - { - return readState; - } + get { return readState; } } public override string Value { - get - { - return value; - } + get { return value; } } public WhitespaceHandling WhitespaceHandling { - get - { - // TODO: implement me. - return WhitespaceHandling.All; - } - - set - { - // TODO: implement me. - } + get { return whitespaceHandling; } + set { whitespaceHandling = value; } } + [MonoTODO] public override string XmlLang { - get - { - // TODO: implement me. - return null; - } + get { throw new NotImplementedException (); } } + [MonoTODO] public XmlResolver XmlResolver { - set - { - // TODO: implement me. - } + set { throw new NotImplementedException (); } } + [MonoTODO] public override XmlSpace XmlSpace { - get - { - // TODO: implement me. - return XmlSpace.Default; - } + get { throw new NotImplementedException (); } } - // methods + #endregion + + #region Methods - public override void Close() + [MonoTODO] + public override void Close () { readState = ReadState.Closed; } - public override string GetAttribute(int i) + [MonoTODO] + public override string GetAttribute (int i) { - // TODO: implement me. - return null; + if (i > attributes.Count) + throw new ArgumentOutOfRangeException ("i is smaller than AttributeCount"); + else + throw new NotImplementedException (); } - public override string GetAttribute(string name) + public override string GetAttribute (string name) { - return (string)attributes[name]; + return attributes.ContainsKey (name) ? + attributes [name] as string : String.Empty; } - public override string GetAttribute( - string localName, - string namespaceURI) + public override string GetAttribute (string localName, string namespaceURI) { foreach (DictionaryEntry entry in attributes) { - string thisName = (string)entry.Key; + string thisName = entry.Key as string; - int indexOfColon = thisName.IndexOf(':'); + int indexOfColon = thisName.IndexOf (':'); - if (indexOfColon != -1) - { - string thisLocalName = thisName.Substring(indexOfColon + 1); + if (indexOfColon != -1) { + string thisLocalName = thisName.Substring (indexOfColon + 1); - if (localName == thisLocalName) - { - string thisPrefix = thisName.Substring(0, indexOfColon); - string thisNamespaceURI = LookupNamespace(thisPrefix); + if (localName == thisLocalName) { + string thisPrefix = thisName.Substring (0, indexOfColon); + string thisNamespaceURI = LookupNamespace (thisPrefix); if (namespaceURI == thisNamespaceURI) - { - return (string)attributes[thisName]; - } + return attributes.ContainsKey (thisName) ? + attributes [thisName] as string : String.Empty; } - } + } else if (localName == "xmlns" && namespaceURI == "http://www.w3.org/2000/xmlns/" && thisName == "xmlns") + return attributes.ContainsKey (thisName) ? + attributes [thisName] as string : String.Empty; } return String.Empty; } - public TextReader GetRemainder() + [MonoTODO] + public TextReader GetRemainder () { - // TODO: implement me. - return null; + throw new NotImplementedException (); } - // Why does this use explicit interface implementation? - bool IXmlLineInfo.HasLineInfo() + [MonoTODO] + bool IXmlLineInfo.HasLineInfo () { - // TODO: implement me. return false; } - public override string LookupNamespace(string prefix) + public override string LookupNamespace (string prefix) { - return namespaceManager.LookupNamespace(prefix); + return parserContext.NamespaceManager.LookupNamespace (prefix); } - public override void MoveToAttribute(int i) + [MonoTODO] + public override void MoveToAttribute (int i) { - // TODO: implement me. + throw new NotImplementedException (); } - public override bool MoveToAttribute(string name) + public override bool MoveToAttribute (string name) { - // TODO: implement me. - return false; + MoveToElement (); + bool match = false; + + if (attributes == null) + return false; + + if (orderedAttributesEnumerator == null) { + SaveProperties (); + orderedAttributesEnumerator = orderedAttributes.GetEnumerator (); + } + + while (orderedAttributesEnumerator.MoveNext ()) { + if(name == orderedAttributesEnumerator.Current as string) { + match = true; + break; + } + } + + if (match) { + + string value = attributes [name] as string; + SetProperties ( + XmlNodeType.Attribute, // nodeType + name, // name + false, // isEmptyElement + value, // value + false // clearAttributes + ); + } + + return match; } - public override bool MoveToAttribute( - string localName, - string namespaceName) + [MonoTODO] + public override bool MoveToAttribute (string localName, string namespaceName) { - // TODO: implement me. - return false; + throw new NotImplementedException (); } - public override bool MoveToElement() + public override bool MoveToElement () { - // TODO: implement me. + if (orderedAttributesEnumerator != null) { + orderedAttributesEnumerator = null; + RestoreProperties (); + return true; + } + return false; } - public override bool MoveToFirstAttribute() + public override bool MoveToFirstAttribute () { - // TODO: implement me. - return false; + MoveToElement (); + return MoveToNextAttribute (); } - public override bool MoveToNextAttribute() + public override bool MoveToNextAttribute () { - // TODO: implement me. + if (attributes == null) + return false; + + if (orderedAttributesEnumerator == null) { + SaveProperties (); + orderedAttributesEnumerator = orderedAttributes.GetEnumerator (); + } + + if (orderedAttributesEnumerator.MoveNext ()) { + string name = orderedAttributesEnumerator.Current as string; + string value = attributes [name] as string; + SetProperties ( + XmlNodeType.Attribute, // nodeType + name, // name + false, // isEmptyElement + value, // value + false // clearAttributes + ); + return true; + } + return false; } - public override bool Read() + public override bool Read () { bool more = false; readState = ReadState.Interactive; - more = ReadContent(); + more = ReadContent (); return more; } - public override bool ReadAttributeValue() + public override bool ReadAttributeValue () { - // TODO: implement me. - return false; + // reading attribute value phase now stopped + if(attributeStringCurrentPosition < 0 || + attributeString.Length < attributeStringCurrentPosition) { + attributeStringCurrentPosition = 0; + attributeString = String.Empty; + return false; + } + + // If not started, then initialize attributeString when parsing is at start. + if(attributeStringCurrentPosition == 0) + attributeString = value; + + bool returnEntity = false; + value = String.Empty; + int nextPosition = attributeString.IndexOf ('&', + attributeStringCurrentPosition); + + // if attribute string starts from '&' then it may be (unparsable) entity reference. + if(nextPosition == 0) { + string parsed = ReadAttributeValueEntityReference (); + if(parsed == null) { + // return entity (It is only this case to return entity reference.) + int endEntityPosition = attributeString.IndexOf (';', + attributeStringCurrentPosition); + SetProperties (XmlNodeType.EntityReference, + attributeString.Substring (attributeStringCurrentPosition + 1, + endEntityPosition - attributeStringCurrentPosition - 1), + false, + String.Empty, + false); + attributeStringCurrentPosition = endEntityPosition + 1; + + return true; + } + else + value += parsed; + } + + // Other case always set text node. + while(!returnEntity) { + nextPosition = attributeString.IndexOf ('&', attributeStringCurrentPosition); + if(nextPosition < 0) { + // Reached to the end of value string. + value += attributeString.Substring (attributeStringCurrentPosition); + attributeStringCurrentPosition = -1; + break; + } else if(nextPosition == attributeStringCurrentPosition) { + string parsed = ReadAttributeValueEntityReference (); + if(parsed != null) + value += parsed; + else { + // Found that an entity reference starts from this point. + // Then once stop to parse attribute value and then return text. + value += attributeString.Substring (attributeStringCurrentPosition, + nextPosition - attributeStringCurrentPosition); + break; + } + } else { + value += attributeString.Substring (attributeStringCurrentPosition, + nextPosition - attributeStringCurrentPosition); + attributeStringCurrentPosition = nextPosition; + break; + } + } + + SetProperties(XmlNodeType.Text, + "#text", + false, + value, + false); + + return true; } - public int ReadBase64(byte[] buffer, int offset, int length) + [MonoTODO] + public int ReadBase64 (byte [] buffer, int offset, int length) { - // TODO: implement me. - return 0; + throw new NotImplementedException (); } - public int ReadBinHex(byte[] buffer, int offset, int length) + [MonoTODO] + public int ReadBinHex (byte [] buffer, int offset, int length) { - // TODO: implement me. - return 0; + throw new NotImplementedException (); } - public int ReadChars(char[] buffer, int offset, int length) + [MonoTODO] + public int ReadChars (char [] buffer, int offset, int length) { - // TODO: implement me. - return 0; + throw new NotImplementedException (); } - public override string ReadInnerXml() + [MonoTODO] + public override string ReadInnerXml () { - // TODO: implement me. - return null; + // Still need a Well Formedness check. + // Will wait for Validating reader ;-) + if (NodeType == XmlNodeType.Attribute) { + return Value; + } else { + saveToXmlBuffer = true; + string startname = this.Name; + string endname = string.Empty; + readState = ReadState.Interactive; + + while (startname != endname) { + ReadContent (); + endname = this.Name; + } + + xmlBuffer.Replace (currentTag.ToString (), ""); + saveToXmlBuffer = false; + string InnerXml = xmlBuffer.ToString (); + xmlBuffer.Length = 0; + return InnerXml; + } } - public override string ReadOuterXml() + [MonoTODO] + public override string ReadOuterXml () { - // TODO: implement me. - return null; + if (NodeType == XmlNodeType.Attribute) { + return Name + "=\"" + Value + "\""; + } else { + saveToXmlBuffer = true; + xmlBuffer.Append (currentTag.ToString ()); + string startname = this.Name; + string endname = string.Empty; + readState = ReadState.Interactive; + + while (startname != endname) { + ReadContent (); + endname = this.Name; + } + saveToXmlBuffer = false; + string OuterXml = xmlBuffer.ToString (); + xmlBuffer.Length = 0; + return OuterXml; + } } - public override string ReadString() + [MonoTODO] + public override string ReadString () { - // TODO: implement me. - return null; + throw new NotImplementedException (); } - public override void ResolveEntity() + [MonoTODO] + public void ResetState () + { + throw new NotImplementedException (); + } + + public override void ResolveEntity () { // XmlTextReaders don't resolve entities. - throw new InvalidOperationException("XmlTextReaders don't resolve entities."); + throw new InvalidOperationException ("XmlTextReaders don't resolve entities."); } - // privates + #endregion + + #region Internals + internal string publicId; + internal string systemId; + + internal void SetReaderContext (string url, XmlParserContext context) + { + parserContext = context; + parserContext.BaseURI = url; + Init (); + } + + internal void SetReaderFragment(TextReader fragment, XmlNodeType fragType) + { + this.reader = fragment; + can_seek = fragment != null && fragment.Peek () != -1; +/* for future use + switch(fragType) + { + case XmlNodeType.Attribute: // attribute content + parserContext.InputState = XmlParserInputState.AttributeValue; + break; + case XmlNodeType.DocumentFragment: // element content + parserContext.InputState = XmlParserInputState.Content; + break; + case XmlNodeType.Element: // one element + parserContext.InputState = XmlParserInputState.StartTag; + break; + case XmlNodeType.Document: // document content + parserContext.InputState = XmlParserInputState.Start; + break; + default: + throw new InvalidOperationException("setting this xml node type not allowed."); + } +*/ + } + #endregion + + #region Privates + + private XmlParserContext parserContext; private TextReader reader; private ReadState readState; private int depth; + private int elementDepth; private bool depthDown; - private XmlNameTable nameTable; - private XmlNamespaceManager namespaceManager; private bool popScope; private XmlNodeType nodeType; @@ -597,49 +677,76 @@ namespace System.Xml private string namespaceURI; private bool isEmptyElement; private string value; + + private XmlNodeType saveNodeType; + private string saveName; + private string savePrefix; + private string saveLocalName; + private string saveNamespaceURI; + private bool saveIsEmptyElement; + private Hashtable attributes; + private ArrayList orderedAttributes; + private IEnumerator orderedAttributesEnumerator; private bool returnEntityReference; private string entityReferenceName; - private char[] nameBuffer; + private char [] nameBuffer; private int nameLength; private int nameCapacity; private const int initialNameCapacity = 256; - private char[] valueBuffer; + private char [] valueBuffer; private int valueLength; private int valueCapacity; private const int initialValueCapacity = 8192; - private void Init() - { - namespaceManager = new XmlNamespaceManager(nameTable); - popScope = false; + private StringBuilder xmlBuffer; // This is for Read(Inner|Outer)Xml + private StringBuilder currentTag; // A buffer for ReadContent for ReadOuterXml + private bool saveToXmlBuffer; + private int line = 1; + private int column = 1; + private bool has_peek; + private bool can_seek; + private int peek_char; + private string attributeString = String.Empty; + private int attributeStringCurrentPosition; + + private void Init () + { readState = ReadState.Initial; - depth = -1; + depth = 0; depthDown = false; + popScope = false; + nodeType = XmlNodeType.None; name = String.Empty; prefix = String.Empty; localName = string.Empty; isEmptyElement = false; value = String.Empty; - attributes = new Hashtable(); - + + attributes = new Hashtable (); + orderedAttributes = new ArrayList (); + orderedAttributesEnumerator = null; + returnEntityReference = false; entityReferenceName = String.Empty; - nameBuffer = new char[initialNameCapacity]; + nameBuffer = new char [initialNameCapacity]; nameLength = 0; nameCapacity = initialNameCapacity; - valueBuffer = new char[initialValueCapacity]; + valueBuffer = new char [initialValueCapacity]; valueLength = 0; valueCapacity = initialValueCapacity; + + xmlBuffer = new StringBuilder (); + currentTag = new StringBuilder (); } // Use this method rather than setting the properties @@ -647,7 +754,7 @@ namespace System.Xml // be changed in harmony with each other. Maybe the // fields should be in a seperate class to help enforce // this. - private void SetProperties( + private void SetProperties ( XmlNodeType nodeType, string name, bool isEmptyElement, @@ -658,108 +765,160 @@ namespace System.Xml this.name = name; this.isEmptyElement = isEmptyElement; this.value = value; + this.elementDepth = depth; if (clearAttributes) - { - ClearAttributes(); - } + ClearAttributes (); - int indexOfColon = name.IndexOf(':'); + int indexOfColon = name.IndexOf (':'); - if (indexOfColon == -1) - { + if (indexOfColon == -1) { prefix = String.Empty; localName = name; - } - else - { - prefix = name.Substring(0, indexOfColon); - localName = name.Substring(indexOfColon + 1); + } else { + prefix = name.Substring (0, indexOfColon); + localName = name.Substring (indexOfColon + 1); } - namespaceURI = LookupNamespace(prefix); + namespaceURI = LookupNamespace (prefix); } - private void AddAttribute(string name, string value) + private void SaveProperties () { - attributes.Add(name, value); + saveNodeType = nodeType; + saveName = name; + savePrefix = prefix; + saveLocalName = localName; + saveNamespaceURI = namespaceURI; + saveIsEmptyElement = isEmptyElement; + // An element's value is always String.Empty. } - private void ClearAttributes() + private void RestoreProperties () { - if (attributes.Count > 0) - { - attributes.Clear(); + nodeType = saveNodeType; + name = saveName; + prefix = savePrefix; + localName = saveLocalName; + namespaceURI = saveNamespaceURI; + isEmptyElement = saveIsEmptyElement; + value = String.Empty; + } + + private void AddAttribute (string name, string value) + { + attributes.Add (name, value); + orderedAttributes.Add (name); + } + + private void ClearAttributes () + { + if (attributes.Count > 0) { + attributes.Clear (); + orderedAttributes.Clear (); } + + orderedAttributesEnumerator = null; } - private int PeekChar() + private int PeekChar () { - return reader.Peek(); + if (can_seek) + return reader.Peek (); + + if (has_peek) + return peek_char; + + peek_char = reader.Read (); + has_peek = true; + return peek_char; } - private int ReadChar() + private int ReadChar () { - return reader.Read(); + int ch; + if (has_peek) { + ch = peek_char; + has_peek = false; + } else { + ch = reader.Read (); + } + + if (ch == '\n') { + line++; + column = 1; + } else { + column++; + } + if (saveToXmlBuffer) { + xmlBuffer.Append ((char) ch); + } + currentTag.Append ((char) ch); + return ch; } // This should really keep track of some state so // that it's not possible to have more than one document // element or text outside of the document element. - private bool ReadContent() + private bool ReadContent () { - bool more = false; - - if (popScope) - { - namespaceManager.PopScope(); + currentTag.Length = 0; + if (popScope) { + parserContext.NamespaceManager.PopScope (); popScope = false; } - if (depthDown) - { - --depth; - } - - if (returnEntityReference) - { - ++depth; - SetEntityReferenceProperties(); - more = true; - } - else - { - switch (PeekChar()) + if (returnEntityReference) { + SetEntityReferenceProperties (); + } else { + switch (PeekChar ()) { case '<': - ReadChar(); - ReadTag(); - more = true; + ReadChar (); + ReadTag (); break; + case '\r': + if (whitespaceHandling == WhitespaceHandling.All || + whitespaceHandling == WhitespaceHandling.Significant) + return ReadWhitespace (); + + ReadChar (); + return ReadContent (); + case '\n': + if (whitespaceHandling == WhitespaceHandling.All || + whitespaceHandling == WhitespaceHandling.Significant) + return ReadWhitespace (); + + ReadChar (); + return ReadContent (); + case ' ': + if (whitespaceHandling == WhitespaceHandling.All || + whitespaceHandling == WhitespaceHandling.Significant) + return ReadWhitespace (); + + SkipWhitespace (); + return ReadContent (); case -1: readState = ReadState.EndOfFile; - SetProperties( + SetProperties ( XmlNodeType.None, // nodeType String.Empty, // name false, // isEmptyElement String.Empty, // value true // clearAttributes ); - more = false; break; default: - ReadText(); - more = true; + ReadText (true); break; } } - - return more; + return this.ReadState != ReadState.EndOfFile; } - private void SetEntityReferenceProperties() + private void SetEntityReferenceProperties () { - SetProperties( + SetProperties ( XmlNodeType.EntityReference, // nodeType entityReferenceName, // name false, // isEmptyElement @@ -772,77 +931,78 @@ namespace System.Xml } // The leading '<' has already been consumed. - private void ReadTag() + private void ReadTag () { - switch (PeekChar()) + switch (PeekChar ()) { case '/': - ReadChar(); - ReadEndTag(); + ReadChar (); + ReadEndTag (); break; case '?': - ReadChar(); - ReadProcessingInstruction(); + ReadChar (); + ReadProcessingInstruction (); break; case '!': - ReadChar(); - ReadDeclaration(); + ReadChar (); + ReadDeclaration (); break; default: - ReadStartTag(); + ReadStartTag (); break; } } // The leading '<' has already been consumed. - private void ReadStartTag() + private void ReadStartTag () { - namespaceManager.PushScope(); + parserContext.NamespaceManager.PushScope (); - string name = ReadName(); - SkipWhitespace(); + string name = ReadName (); + SkipWhitespace (); bool isEmptyElement = false; - ClearAttributes(); + ClearAttributes (); - if (XmlChar.IsFirstNameChar(PeekChar())) - { - ReadAttributes(); - } + if (XmlChar.IsFirstNameChar (PeekChar ())) + ReadAttributes (); - if (PeekChar() == '/') - { - ReadChar(); + if (PeekChar () == '/') { + ReadChar (); isEmptyElement = true; depthDown = true; popScope = true; } - Expect('>'); - - ++depth; + Expect ('>'); - SetProperties( + SetProperties ( XmlNodeType.Element, // nodeType name, // name isEmptyElement, // isEmptyElement String.Empty, // value false // clearAttributes ); + + if (!depthDown) + ++depth; + else + depthDown = false; + } // The reader is positioned on the first character // of the element's name. - private void ReadEndTag() + private void ReadEndTag () { - string name = ReadName(); - SkipWhitespace(); - Expect('>'); + string name = ReadName (); + SkipWhitespace (); + Expect ('>'); --depth; - SetProperties( + SetProperties ( XmlNodeType.EndElement, // nodeType name, // name false, // isEmptyElement @@ -853,95 +1013,76 @@ namespace System.Xml popScope = true; } - private void AppendNameChar(int ch) + private void AppendNameChar (int ch) { - CheckNameCapacity(); - nameBuffer[nameLength++] = (char)ch; + CheckNameCapacity (); + nameBuffer [nameLength++] = (char)ch; } - private void CheckNameCapacity() + private void CheckNameCapacity () { - if (nameLength == nameCapacity) - { + if (nameLength == nameCapacity) { nameCapacity = nameCapacity * 2; - char[] oldNameBuffer = nameBuffer; - nameBuffer = new char[nameCapacity]; - Array.Copy(oldNameBuffer, nameBuffer, nameLength); + char [] oldNameBuffer = nameBuffer; + nameBuffer = new char [nameCapacity]; + Array.Copy (oldNameBuffer, nameBuffer, nameLength); } } - private string CreateNameString() + private string CreateNameString () { - return new String(nameBuffer, 0, nameLength); + return new String (nameBuffer, 0, nameLength); } - private void AppendValueChar(int ch) + private void AppendValueChar (int ch) { - CheckValueCapacity(); - valueBuffer[valueLength++] = (char)ch; + CheckValueCapacity (); + valueBuffer [valueLength++] = (char)ch; } - private void CheckValueCapacity() + private void CheckValueCapacity () { - if (valueLength == valueCapacity) - { + if (valueLength == valueCapacity) { valueCapacity = valueCapacity * 2; - char[] oldValueBuffer = valueBuffer; - valueBuffer = new char[valueCapacity]; - Array.Copy(oldValueBuffer, valueBuffer, valueLength); + char [] oldValueBuffer = valueBuffer; + valueBuffer = new char [valueCapacity]; + Array.Copy (oldValueBuffer, valueBuffer, valueLength); } } - private string CreateValueString() + private string CreateValueString () { - return new String(valueBuffer, 0, valueLength); + return new String (valueBuffer, 0, valueLength); } // The reader is positioned on the first character // of the text. - private void ReadText() + private void ReadText (bool cleanValue) { - valueLength = 0; + if (cleanValue) + valueLength = 0; - int ch = PeekChar(); + int ch = PeekChar (); - while (ch != '<' && ch != -1) - { - if (ch == '&') - { - ReadChar(); - - if (ReadReference(false)) - { + while (ch != '<' && ch != -1) { + if (ch == '&') { + ReadChar (); + if (ReadReference (false)) break; - } - } - else - { - AppendValueChar(ReadChar()); - } + } else + AppendValueChar (ReadChar ()); - ch = PeekChar(); - } - - if (returnEntityReference && valueLength == 0) - { - ++depth; - SetEntityReferenceProperties(); + ch = PeekChar (); } - else - { - if (depth >= 0) - { - ++depth; - depthDown = true; - } - SetProperties( + if (returnEntityReference && valueLength == 0) { + SetEntityReferenceProperties (); + } else { + SetProperties ( XmlNodeType.Text, // nodeType String.Empty, // name false, // isEmptyElement - CreateValueString(), // value + CreateValueString (), // value true // clearAttributes ); } @@ -952,128 +1093,102 @@ namespace System.Xml // character reference or one of the predefined entities. // This allows the ReadText method to break so that the // next call to Read will return the EntityReference node. - private bool ReadReference(bool ignoreEntityReferences) + private bool ReadReference (bool ignoreEntityReferences) { - if (PeekChar() == '#') - { - ReadChar(); - ReadCharacterReference(); - } - else - { - ReadEntityReference(ignoreEntityReferences); - } + if (PeekChar () == '#') { + ReadChar (); + ReadCharacterReference (); + } else + ReadEntityReference (ignoreEntityReferences); return returnEntityReference; } - private void ReadCharacterReference() + private void ReadCharacterReference () { int value = 0; - if (PeekChar() == 'x') - { - ReadChar(); + if (PeekChar () == 'x') { + ReadChar (); - while (PeekChar() != ';' && PeekChar() != -1) - { - int ch = ReadChar(); + while (PeekChar () != ';' && PeekChar () != -1) { + int ch = ReadChar (); if (ch >= '0' && ch <= '9') - { value = (value << 4) + ch - '0'; - } else if (ch >= 'A' && ch <= 'F') - { value = (value << 4) + ch - 'A' + 10; - } else if (ch >= 'a' && ch <= 'f') - { value = (value << 4) + ch - 'a' + 10; - } else - { - throw new Exception( - String.Format( + throw new XmlException ( + String.Format ( "invalid hexadecimal digit: {0} (#x{1:X})", (char)ch, ch)); - } } - } - else - { - while (PeekChar() != ';' && PeekChar() != -1) - { - int ch = ReadChar(); + } else { + while (PeekChar () != ';' && PeekChar () != -1) { + int ch = ReadChar (); if (ch >= '0' && ch <= '9') - { value = value * 10 + ch - '0'; - } else - { - throw new Exception( - String.Format( + throw new XmlException ( + String.Format ( "invalid decimal digit: {0} (#x{1:X})", (char)ch, ch)); - } } } - ReadChar(); // ';' + ReadChar (); // ';' - AppendValueChar(value); + AppendValueChar (value); } - private void ReadEntityReference(bool ignoreEntityReferences) + private void ReadEntityReference (bool ignoreEntityReferences) { nameLength = 0; - int ch = PeekChar(); + int ch = PeekChar (); - while (ch != ';' && ch != -1) - { - AppendNameChar(ReadChar()); - ch = PeekChar(); + while (ch != ';' && ch != -1) { + AppendNameChar (ReadChar ()); + ch = PeekChar (); } - Expect(';'); + Expect (';'); - string name = CreateNameString(); + string name = CreateNameString (); switch (name) { case "lt": - AppendValueChar('<'); + AppendValueChar ('<'); break; case "gt": - AppendValueChar('>'); + AppendValueChar ('>'); break; case "amp": - AppendValueChar('&'); + AppendValueChar ('&'); break; case "apos": - AppendValueChar('\''); + AppendValueChar ('\''); break; case "quot": - AppendValueChar('"'); + AppendValueChar ('"'); break; default: - if (ignoreEntityReferences) - { - AppendValueChar('&'); + if (ignoreEntityReferences) { + AppendValueChar ('&'); - foreach (char ch2 in name) - { - AppendValueChar(ch2); + foreach (char ch2 in name) { + AppendValueChar (ch2); } - AppendValueChar(';'); - } - else - { + AppendValueChar (';'); + } else { returnEntityReference = true; entityReferenceName = name; } @@ -1083,247 +1198,527 @@ namespace System.Xml // The reader is positioned on the first character of // the attribute name. - private void ReadAttributes() + private void ReadAttributes () { - do - { - string name = ReadName(); - SkipWhitespace(); - Expect('='); - SkipWhitespace(); - string value = ReadAttribute(); - SkipWhitespace(); + do { + string name = ReadName (); + SkipWhitespace (); + Expect ('='); + SkipWhitespace (); + string value = ReadAttribute (); + SkipWhitespace (); if (name == "xmlns") - { - namespaceManager.AddNamespace(String.Empty, value); - } - else if (name.StartsWith("xmlns:")) - { - namespaceManager.AddNamespace(name.Substring(6), value); - } - else - { - AddAttribute(name, value); - } - } - while (PeekChar() != '/' && PeekChar() != '>' && PeekChar() != -1); + parserContext.NamespaceManager.AddNamespace (String.Empty, value); + else if (name.StartsWith ("xmlns:")) + parserContext.NamespaceManager.AddNamespace (name.Substring (6), value); + + AddAttribute (name, value); + } while (PeekChar () != '/' && PeekChar () != '>' && PeekChar () != -1); } // The reader is positioned on the quote character. - private string ReadAttribute() + private string ReadAttribute () { - int quoteChar = ReadChar(); + int quoteChar = ReadChar (); if (quoteChar != '\'' && quoteChar != '\"') - { - throw new Exception("an attribute value was not quoted"); - } + throw new XmlException ("an attribute value was not quoted"); valueLength = 0; - while (PeekChar() != quoteChar) - { - int ch = ReadChar(); + while (PeekChar () != quoteChar) { + int ch = ReadChar (); switch (ch) { case '<': - throw new Exception("attribute values cannot contain '<'"); + throw new XmlException ("attribute values cannot contain '<'"); case '&': - ReadReference(true); + ReadReference (true); break; case -1: - throw new Exception("unexpected end of file in an attribute value"); + throw new XmlException ("unexpected end of file in an attribute value"); default: - AppendValueChar(ch); + AppendValueChar (ch); break; } } - ReadChar(); // quoteChar + ReadChar (); // quoteChar - return CreateValueString(); + return CreateValueString (); } // The reader is positioned on the first character // of the target. - private void ReadProcessingInstruction() + // + // Now it also reads XmlDeclaration, this method name became improper... + private void ReadProcessingInstruction () { - string target = ReadName(); - SkipWhitespace(); + string target = ReadName (); + SkipWhitespace (); valueLength = 0; - while (PeekChar() != -1) - { - int ch = ReadChar(); + while (PeekChar () != -1) { + int ch = ReadChar (); - if (ch == '?' && PeekChar() == '>') - { - ReadChar(); + if (ch == '?' && PeekChar () == '>') { + ReadChar (); break; } - AppendValueChar((char)ch); + AppendValueChar ((char)ch); } - SetProperties( +/* for future use + if(target == "xml") && parserContext.InputState != XmlParserInputState.Start) + throw new XmlException("Xml declaration is not allowed here."); + else { + parserContext.InputState = XmlParserInputState.DTD; //for future use + } +*/ + SetProperties ( + target == "xml" ? + XmlNodeType.XmlDeclaration : XmlNodeType.ProcessingInstruction, // nodeType target, // name false, // isEmptyElement - CreateValueString(), // value + CreateValueString (), // value true // clearAttributes ); } // The reader is positioned on the first character after // the leading '') - { - throw new Exception("comments cannot contain '--'"); - } + if (PeekChar () != '>') + throw new XmlException ("comments cannot contain '--'"); - ReadChar(); + ReadChar (); break; } - AppendValueChar((char)ch); + AppendValueChar ((char)ch); } - SetProperties( + SetProperties ( XmlNodeType.Comment, // nodeType String.Empty, // name false, // isEmptyElement - CreateValueString(), // value + CreateValueString (), // value true // clearAttributes ); } // The reader is positioned on the first character after // the leading '') - { - ReadChar(); // '>' + if (PeekChar () == '>') { + ReadChar (); // '>' break; - } - else - { - AppendValueChar(']'); - AppendValueChar(']'); - ch = ReadChar(); + } else { + AppendValueChar (']'); + AppendValueChar (']'); + ch = ReadChar (); } } - AppendValueChar((char)ch); + AppendValueChar ((char)ch); } - ++depth; - - SetProperties( + SetProperties ( XmlNodeType.CDATA, // nodeType String.Empty, // name false, // isEmptyElement - CreateValueString(), // value + CreateValueString (), // value true // clearAttributes ); } - // The reader is positioned on the first character - // of the name. - private string ReadName() + // The reader is positioned on the first character after + // the leading ''); + + parserContext.InternalSubset = xmlBuffer.ToString (); + + // set properties for node + SetProperties ( + XmlNodeType.DocumentType, // nodeType + doctypeName, // name + false, // isEmptyElement + parserContext.InternalSubset, // value + true // clearAttributes + ); + } - while (XmlChar.IsNameChar(PeekChar())) + // Read any one of following: + // elementdecl, AttlistDecl, EntityDecl, NotationDecl, + // PI, Comment, Parameter Entity, or doctype termination char(']') + // + // returns a node of some nodeType or null, setting nodeType. + // (if None then ']' was found.) + private void ReadDTDInternalSubset() + { + SkipWhitespace (); + switch(ReadChar ()) { - AppendNameChar(ReadChar()); + case ']': + nodeType = XmlNodeType.None; + break; + case '%': + string peName = ReadName (); + Expect (';'); + nodeType = XmlNodeType.EntityReference; // It's chating a bit;-) + break; + case '<': + switch(ReadChar ()) + { + case '?': + ReadProcessingInstruction (); + break; + case '!': + switch(ReadChar ()) + { + case '-': + Expect ('-'); + ReadComment (); + break; + case 'E': + switch(ReadChar ()) + { + case 'N': + Expect ("TITY"); + ReadEntityDecl (); + break; + case 'L': + Expect ("EMENT"); + ReadElementDecl (); + break; + default: + throw new XmlException ("Syntax Error after ''); + } - return CreateNameString(); + private void ReadEntityDecl() + { + while(ReadChar () != '>'); + } + + private void ReadAttListDecl() + { + while(ReadChar () != '>'); + } + + private void ReadNotationDecl() + { + while(ReadChar () != '>'); + } + + // The reader is positioned on the first 'S' of "SYSTEM". + private string ReadSystemLiteral (bool expectSYSTEM) + { + if(expectSYSTEM) + Expect ("SYSTEM"); + SkipWhitespace (); + int quoteChar = ReadChar (); // apos or quot + xmlBuffer.Length = 0; + saveToXmlBuffer = true; + int c = 0; + while(c != quoteChar) { + c = ReadChar (); + if(c < 0) throw new XmlException ("Unexpected end of stream in ExternalID."); + } + saveToXmlBuffer = false; + xmlBuffer.Remove (xmlBuffer.Length-1, 1); // cut quoteChar + return xmlBuffer.ToString (); + } + + private string ReadPubidLiteral() + { + Expect ("PUBLIC"); + SkipWhitespace (); + int quoteChar = ReadChar (); + xmlBuffer.Length = 0; + saveToXmlBuffer = true; + int c = 0; + while(c != quoteChar) + { + c = ReadChar (); + if(c < 0) throw new XmlException ("Unexpected end of stream in ExternalID."); + if(c != quoteChar && !XmlChar.IsPubidChar (c)) + throw new XmlException("character '" + (char)c + "' not allowed for PUBLIC ID"); + } + ReadChar(); // skips quoteChar + xmlBuffer.Remove (xmlBuffer.Length-1, 1); // cut quoteChar + saveToXmlBuffer = false; + return xmlBuffer.ToString (); + } + + // The reader is positioned on the first character + // of the name. + private string ReadName () + { + if (!XmlChar.IsFirstNameChar (PeekChar ())) + throw new XmlException ("a name did not start with a legal character"); + + nameLength = 0; + + AppendNameChar (ReadChar ()); + + while (XmlChar.IsNameChar (PeekChar ())) { + AppendNameChar (ReadChar ()); + } + + return CreateNameString (); } // Read the next character and compare it against the // specified character. - private void Expect(int expected) + private void Expect (int expected) { - int ch = ReadChar(); + int ch = ReadChar (); - if (ch != expected) - { - throw new Exception(String.Format( - "expected '{0}' ({1:X}) but found '{2}' ({3:X})", - (char)expected, - expected, - (char)ch, - ch)); + if (ch != expected) { + throw new XmlException ( + String.Format ( + "expected '{0}' ({1:X}) but found '{2}' ({3:X})", + (char)expected, + expected, + (char)ch, + ch)); } } + private void Expect (string expected) + { + int len = expected.Length; + for(int i=0; i< len; i++) + Expect (expected[i]); + } + // Does not consume the first non-whitespace character. - private void SkipWhitespace() + private void SkipWhitespace () { - while (XmlChar.IsWhitespace(PeekChar())) - { - ReadChar(); + //FIXME: Should not skip if whitespaceHandling == WhiteSpaceHandling.None + while (XmlChar.IsWhitespace (PeekChar ())) + ReadChar (); + } + + private bool ReadWhitespace () + { + valueLength = 0; + int ch = PeekChar (); + do { + AppendValueChar (ReadChar ()); + } while ((ch = PeekChar ()) != -1 && XmlChar.IsWhitespace (ch)); + + if (ch != -1 && ch != '<') + ReadText (false); + else + SetProperties (XmlNodeType.Whitespace, + String.Empty, + false, + CreateValueString (), + true); + + return (PeekChar () != -1); + } + + // read entity reference from attribute string and if parsable then return the value. + private string ReadAttributeValueEntityReference () + { + int endEntityPosition = attributeString.IndexOf(';', + attributeStringCurrentPosition); + string entityName = attributeString.Substring (attributeStringCurrentPosition + 1, + endEntityPosition - attributeStringCurrentPosition - 1); + + attributeStringCurrentPosition = endEntityPosition + 1; + + if(entityName [0] == '#') { + char c; + // character entity + if(entityName [1] == 'x') { + // hexadecimal + c = (char) int.Parse ("0" + entityName.Substring (2), + System.Globalization.NumberStyles.HexNumber); + } else { + // decimal + c = (char) int.Parse (entityName.Substring (1)); + } + return c.ToString(); } + else { + switch(entityName) + { + case "lt": return "<"; + case "gt": return ">"; + case "amp": return "&"; + case "quot": return "\""; + case "apos": return "'"; + default: return null; + } + } + } + + private string ResolveAttributeValue (string unresolved) + { + if(unresolved == null) return null; + StringBuilder resolved = new StringBuilder(); + int pos = 0; + + int next = unresolved.IndexOf ('&'); + if(next < 0) + return unresolved; + + while(next >= 0) { + if(pos < next) + resolved.Append (unresolved.Substring (pos, next - pos));// - 1); + int endPos = unresolved.IndexOf (';', next+1); + string entityName = + unresolved.Substring (next + 1, endPos - next - 1); + if(entityName [0] == '#') { + char c; + // character entity + if(entityName [1] == 'x') { + // hexadecimal + c = (char) int.Parse ("0" + entityName.Substring (2), + System.Globalization.NumberStyles.HexNumber); + } else { + // decimal + c = (char) int.Parse (entityName.Substring (1)); + } + resolved.Append (c); + } else { + switch(entityName) { + case "lt": resolved.Append ("<"); break; + case "gt": resolved.Append (">"); break; + case "amp": resolved.Append ("&"); break; + case "quot": resolved.Append ("\""); break; + case "apos": resolved.Append ("'"); break; + // With respect to "Value", MS document is helpless + // and the implemention returns inconsistent value + // (e.g. XML: "&ent; &ent;" ---> Value: "&ent; &ent;".) + default: resolved.Append ("&" + entityName + ";"); break; + } + } + pos = endPos + 1; + if(pos > unresolved.Length) + break; + next = unresolved.IndexOf('&', pos); + } + resolved.Append (unresolved.Substring(pos)); + + return resolved.ToString(); } + + #endregion } }