From 4e6fdc33d6ca4e41a24813e1d23b9e836e3909b9 Mon Sep 17 00:00:00 2001 From: ceee Date: Tue, 15 Oct 2013 21:09:22 +0200 Subject: [PATCH] update SgmlReader Port to include HTML DTD --- PocketSharp/PocketReader.cs | 2 +- PocketSharp/PocketSharp.csproj | 2 +- .../NReadability/NReadability.csproj | 6 +- PortablePorts/SgmlReader/HTMLspecial.ent | 77 + PortablePorts/SgmlReader/HTMLsymbol.ent | 241 + PortablePorts/SgmlReader/SgmlParser.cs | 6480 ++++++++--------- PortablePorts/SgmlReader/SgmlReader.cs | 5187 ++++++------- PortablePorts/SgmlReader/SgmlReader.csproj | 6 +- PortablePorts/SgmlReader/htmllat1.ent | 194 + 9 files changed, 6350 insertions(+), 5845 deletions(-) create mode 100644 PortablePorts/SgmlReader/HTMLspecial.ent create mode 100644 PortablePorts/SgmlReader/HTMLsymbol.ent create mode 100644 PortablePorts/SgmlReader/htmllat1.ent diff --git a/PocketSharp/PocketReader.cs b/PocketSharp/PocketReader.cs index a2c5c1f..8074f00 100644 --- a/PocketSharp/PocketReader.cs +++ b/PocketSharp/PocketReader.cs @@ -57,7 +57,7 @@ namespace PocketSharp string htmlResponse = await Request(uri); - TranscodingInput input = new TranscodingInput(htmlResponse); + TranscodingInput input = new TranscodingInput("

HALLO

was geht

"); TranscodingResult result = transcoder.Transcode(input); // //new TranscodingInput( diff --git a/PocketSharp/PocketSharp.csproj b/PocketSharp/PocketSharp.csproj index 1767bb8..962b3a1 100644 --- a/PocketSharp/PocketSharp.csproj +++ b/PocketSharp/PocketSharp.csproj @@ -11,7 +11,7 @@ PocketSharp PocketSharp v4.0 - Profile96 + Profile104 512 {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} ..\ diff --git a/PortablePorts/NReadability/NReadability.csproj b/PortablePorts/NReadability/NReadability.csproj index 93fead0..b4cc528 100644 --- a/PortablePorts/NReadability/NReadability.csproj +++ b/PortablePorts/NReadability/NReadability.csproj @@ -1,4 +1,4 @@ - + @@ -11,7 +11,7 @@ NReadability NReadability v4.0 - Profile96 + Profile104 512 {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -73,4 +73,4 @@ --> - + \ No newline at end of file diff --git a/PortablePorts/SgmlReader/HTMLspecial.ent b/PortablePorts/SgmlReader/HTMLspecial.ent new file mode 100644 index 0000000..5ce5c6a --- /dev/null +++ b/PortablePorts/SgmlReader/HTMLspecial.ent @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PortablePorts/SgmlReader/HTMLsymbol.ent b/PortablePorts/SgmlReader/HTMLsymbol.ent new file mode 100644 index 0000000..524bfe1 --- /dev/null +++ b/PortablePorts/SgmlReader/HTMLsymbol.ent @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PortablePorts/SgmlReader/SgmlParser.cs b/PortablePorts/SgmlReader/SgmlParser.cs index 2ec3564..1daf38d 100644 --- a/PortablePorts/SgmlReader/SgmlParser.cs +++ b/PortablePorts/SgmlReader/SgmlParser.cs @@ -1,4 +1,4 @@ -/* +/* * * Copyright (c) 2007-2013 MindTouch. All rights reserved. * www.mindtouch.com oss@mindtouch.com @@ -31,3398 +31,3374 @@ using System.Text; using System.Threading; using System.Xml; -namespace Sgml -{ - /// - /// Thrown if any errors occur while parsing the source. - /// - public class SgmlParseException : Exception - { - private string m_entityContext; - +namespace Sgml { /// - /// Instantiates a new instance of SgmlParseException with no specific error information. + /// Thrown if any errors occur while parsing the source. /// - public SgmlParseException() +#if !PORTABLE + [Serializable] +#endif + public class SgmlParseException : Exception { - } + private string m_entityContext; - /// - /// Instantiates a new instance of SgmlParseException with an error message describing the problem. - /// - /// A message describing the error that occurred - public SgmlParseException(string message) - : base(message) - { - } - - /// - /// Instantiates a new instance of SgmlParseException with an error message describing the problem. - /// - /// A message describing the error that occurred - /// The entity on which the error occurred. - public SgmlParseException(string message, Entity e) - : base(message) - { - if (e != null) - m_entityContext = e.Context(); - } - - /// - /// Instantiates a new instance of SgmlParseException with an error message describing the problem. - /// - /// A message describing the error that occurred - /// The original exception that caused the problem. - public SgmlParseException(string message, Exception innerException) - : base(message, innerException) - { - } - - /// - /// Contextual information detailing the entity on which the error occurred. - /// - public string EntityContext - { - get - { - return m_entityContext; - } - } - - } - - /// - /// The different types of literal text returned by the SgmlParser. - /// - public enum LiteralType - { - /// - /// CDATA text literals. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - CDATA, - - /// - /// SDATA entities. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - SDATA, - - /// - /// The contents of a Processing Instruction. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - PI - }; - - /// - /// An Entity declared in a DTD. - /// - public class Entity : IDisposable - { - /// - /// The character indicating End Of File. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "The capitalisation is correct since EOF is an acronym.")] - public const char EOF = (char)65535; - - private string m_proxy; - private string m_name; - private bool m_isInternal; - private string m_publicId; - private string m_uri; - private string m_literal; - private LiteralType m_literalType; - private Entity m_parent; - private bool m_isHtml; - private int m_line; - private char m_lastchar; - private bool m_isWhitespace; - - private Encoding m_encoding; - private Uri m_resolvedUri; - private TextReader m_stm; - private bool m_weOwnTheStream; - private int m_lineStart; - private int m_absolutePos; - - /// - /// Initialises a new instance of an Entity declared in a DTD. - /// - /// The name of the entity. - /// The public id of the entity. - /// The uri of the entity. - /// The proxy server to use when retrieving any web content. - public Entity(string name, string pubid, string uri, string proxy) - { - m_name = name; - m_publicId = pubid; - m_uri = uri; - m_proxy = proxy; - m_isHtml = (name != null && StringUtilities.EqualsIgnoreCase(name, "html")); - } - - /// - /// Initialises a new instance of an Entity declared in a DTD. - /// - /// The name of the entity. - /// The literal value of the entity. - public Entity(string name, string literal) - { - m_name = name; - m_literal = literal; - m_isInternal = true; - } - - /// - /// Initialises a new instance of an Entity declared in a DTD. - /// - /// The name of the entity. - /// The baseUri for the entity to read from the TextReader. - /// The TextReader to read the entity from. - /// The proxy server to use when retrieving any web content. - public Entity(string name, Uri baseUri, TextReader stm, string proxy) - { - m_name = name; - m_isInternal = true; - m_stm = stm; - m_resolvedUri = baseUri; - m_proxy = proxy; - m_isHtml = string.Equals(name, "html", StringComparison.OrdinalIgnoreCase); - } - - /// - /// The name of the entity. - /// - public string Name - { - get - { - return m_name; - } - } - - /// - /// True if the entity is the html element entity. - /// - public bool IsHtml - { - get - { - return m_isHtml; - } - set - { - m_isHtml = value; - } - } - - /// - /// The public identifier of this entity. - /// - public string PublicId - { - get - { - return m_publicId; - } - } - - /// - /// The Uri that is the source for this entity. - /// - public string Uri - { - get - { - return m_uri; - } - } - - /// - /// The resolved location of the DTD this entity is from. - /// - public Uri ResolvedUri - { - get - { - if (this.m_resolvedUri != null) - return this.m_resolvedUri; - else if (m_parent != null) - return m_parent.ResolvedUri; - else - return null; - } - } - - /// - /// Gets the parent Entity of this Entity. - /// - public Entity Parent - { - get - { - return m_parent; - } - } - - /// - /// The last character read from the input stream for this entity. - /// - public char Lastchar - { - get - { - return m_lastchar; - } - } - - /// - /// The line on which this entity was defined. - /// - public int Line - { - get - { - return m_line; - } - } - - /// - /// The index into the line where this entity is defined. - /// - public int LinePosition - { - get - { - return this.m_absolutePos - this.m_lineStart + 1; - } - } - - /// - /// Whether this entity is an internal entity or not. - /// - /// true if this entity is internal, otherwise false. - public bool IsInternal - { - get - { - return m_isInternal; - } - } - - /// - /// The literal value of this entity. - /// - public string Literal - { - get - { - return m_literal; - } - } - - /// - /// The of this entity. - /// - public LiteralType LiteralType - { - get - { - return m_literalType; - } - } - - /// - /// Whether the last char read for this entity is a whitespace character. - /// - public bool IsWhitespace - { - get - { - return m_isWhitespace; - } - } - - /// - /// The proxy server to use when making web requests to resolve entities. - /// - public string Proxy - { - get - { - return m_proxy; - } - } - - /// - /// Reads the next character from the DTD stream. - /// - /// The next character from the DTD stream. - public char ReadChar() - { - char ch = (char)this.m_stm.Read(); - if (ch == 0) - { - // convert nulls to whitespace, since they are not valid in XML anyway. - ch = ' '; - } - this.m_absolutePos++; - if (ch == 0xa) - { - m_isWhitespace = true; - this.m_lineStart = this.m_absolutePos + 1; - this.m_line++; - } - else if (ch == ' ' || ch == '\t') - { - m_isWhitespace = true; - if (m_lastchar == 0xd) + /// + /// Instantiates a new instance of SgmlParseException with no specific error information. + /// + public SgmlParseException() { - this.m_lineStart = this.m_absolutePos; - m_line++; - } - } - else if (ch == 0xd) - { - m_isWhitespace = true; - } - else - { - m_isWhitespace = false; - if (m_lastchar == 0xd) - { - m_line++; - this.m_lineStart = this.m_absolutePos; - } - } - - m_lastchar = ch; - return ch; - } - - /// - /// Begins processing an entity. - /// - /// The parent of this entity. - /// The base Uri for processing this entity within. - public void Open(Entity parent, Uri baseUri) - { - this.m_parent = parent; - if (parent != null) - this.m_isHtml = parent.IsHtml; - this.m_line = 1; - if (m_isInternal) - { - if (this.m_literal != null) - this.m_stm = new StringReader(this.m_literal); - } - else if (this.m_uri == null) - { - this.Error("Unresolvable entity '{0}'", this.m_name); - } - else - { - if (baseUri != null) - { - this.m_resolvedUri = new Uri(baseUri, this.m_uri); - } - else - { - this.m_resolvedUri = new Uri(this.m_uri); } - Stream stream = null; - Encoding e = Encoding.UTF8; - switch (this.m_resolvedUri.Scheme) + /// + /// Instantiates a new instance of SgmlParseException with an error message describing the problem. + /// + /// A message describing the error that occurred + public SgmlParseException(string message) + : base(message) { - case "file": - { - throw new NotSupportedException("The file scheme is not supported by the .NETPortable framework."); - } - default: - //Console.WriteLine("Fetching:" + ResolvedUri.AbsoluteUri); - HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(ResolvedUri); - WebResponse resp = GetResponse(wr, TimeSpan.FromSeconds(10)); - Uri actual = resp.ResponseUri; - if (!string.Equals(actual.AbsoluteUri, this.m_resolvedUri.AbsoluteUri, StringComparison.OrdinalIgnoreCase)) - { - this.m_resolvedUri = actual; - } - string contentType = resp.ContentType.ToLowerInvariant(); - string mimeType = contentType; - int i = contentType.IndexOf(';'); - if (i >= 0) - { - mimeType = contentType.Substring(0, i); - } + } - if (StringUtilities.EqualsIgnoreCase(mimeType, "text/html")) + /// + /// Instantiates a new instance of SgmlParseException with an error message describing the problem. + /// + /// A message describing the error that occurred + /// The entity on which the error occurred. + public SgmlParseException(string message, Entity e) + : base(message) + { + if (e != null) + m_entityContext = e.Context(); + } + + /// + /// Instantiates a new instance of SgmlParseException with an error message describing the problem. + /// + /// A message describing the error that occurred + /// The original exception that caused the problem. + public SgmlParseException(string message, Exception innerException) + : base(message, innerException) + { + } + +#if !PORTABLE + /// + /// Initializes a new instance of the SgmlParseException class with serialized data. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. + protected SgmlParseException(System.Runtime.Serialization.SerializationInfo streamInfo, System.Runtime.Serialization.StreamingContext streamCtx) + : base(streamInfo, streamCtx) + { + if (streamInfo != null) + m_entityContext = streamInfo.GetString("entityContext"); + } + + /// + /// Populates a SerializationInfo with the data needed to serialize the exception. + /// + /// The to populate with data. + /// The destination (see ) for this serialization. + [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + { + if (info == null) + throw new ArgumentNullException("info"); + + info.AddValue("entityContext", m_entityContext); + base.GetObjectData(info, context); + } +#endif + + /// + /// Contextual information detailing the entity on which the error occurred. + /// + public string EntityContext + { + get { - this.m_isHtml = true; + return m_entityContext; } + } - i = contentType.IndexOf("charset"); - e = Encoding.UTF8; + } - if (i >= 0) + /// + /// The different types of literal text returned by the SgmlParser. + /// + public enum LiteralType + { + /// + /// CDATA text literals. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + CDATA, + + /// + /// SDATA entities. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + SDATA, + + /// + /// The contents of a Processing Instruction. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + PI + }; + + /// + /// An Entity declared in a DTD. + /// + public class Entity : IDisposable + { + /// + /// The character indicating End Of File. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "The capitalisation is correct since EOF is an acronym.")] + public const char EOF = (char)65535; + + private string m_proxy; + private string m_name; + private bool m_isInternal; + private string m_publicId; + private string m_uri; + private string m_literal; + private LiteralType m_literalType; + private Entity m_parent; + private bool m_isHtml; + private int m_line; + private char m_lastchar; + private bool m_isWhitespace; + + private Encoding m_encoding; + private Uri m_resolvedUri; + private TextReader m_stm; + private bool m_weOwnTheStream; + private int m_lineStart; + private int m_absolutePos; + + /// + /// Initialises a new instance of an Entity declared in a DTD. + /// + /// The name of the entity. + /// The public id of the entity. + /// The uri of the entity. + /// The proxy server to use when retrieving any web content. + public Entity(string name, string pubid, string uri, string proxy) + { + m_name = name; + m_publicId = pubid; + m_uri = uri; + m_proxy = proxy; + m_isHtml = (name != null && StringUtilities.EqualsIgnoreCase(name, "html")); + } + + /// + /// Initialises a new instance of an Entity declared in a DTD. + /// + /// The name of the entity. + /// The literal value of the entity. + public Entity(string name, string literal) + { + m_name = name; + m_literal = literal; + m_isInternal = true; + } + + /// + /// Initialises a new instance of an Entity declared in a DTD. + /// + /// The name of the entity. + /// The baseUri for the entity to read from the TextReader. + /// The TextReader to read the entity from. + /// The proxy server to use when retrieving any web content. + public Entity(string name, Uri baseUri, TextReader stm, string proxy) + { + m_name = name; + m_isInternal = true; + m_stm = stm; + m_resolvedUri = baseUri; + m_proxy = proxy; + m_isHtml = string.Equals(name, "html", StringComparison.OrdinalIgnoreCase); + } + + /// + /// The name of the entity. + /// + public string Name + { + get { - int j = contentType.IndexOf("=", i); - int k = contentType.IndexOf(";", j); - if (k < 0) - k = contentType.Length; + return m_name; + } + } - if (j > 0) - { - j++; - string charset = contentType.Substring(j, k - j).Trim(); - try + /// + /// True if the entity is the html element entity. + /// + public bool IsHtml + { + get + { + return m_isHtml; + } + set + { + m_isHtml = value; + } + } + + /// + /// The public identifier of this entity. + /// + public string PublicId + { + get + { + return m_publicId; + } + } + + /// + /// The Uri that is the source for this entity. + /// + public string Uri + { + get + { + return m_uri; + } + } + + /// + /// The resolved location of the DTD this entity is from. + /// + public Uri ResolvedUri + { + get + { + if (this.m_resolvedUri != null) + return this.m_resolvedUri; + else if (m_parent != null) + return m_parent.ResolvedUri; + else + return null; + } + } + + /// + /// Gets the parent Entity of this Entity. + /// + public Entity Parent + { + get + { + return m_parent; + } + } + + /// + /// The last character read from the input stream for this entity. + /// + public char Lastchar + { + get + { + return m_lastchar; + } + } + + /// + /// The line on which this entity was defined. + /// + public int Line + { + get + { + return m_line; + } + } + + /// + /// The index into the line where this entity is defined. + /// + public int LinePosition + { + get + { + return this.m_absolutePos - this.m_lineStart + 1; + } + } + + /// + /// Whether this entity is an internal entity or not. + /// + /// true if this entity is internal, otherwise false. + public bool IsInternal + { + get + { + return m_isInternal; + } + } + + /// + /// The literal value of this entity. + /// + public string Literal + { + get + { + return m_literal; + } + } + + /// + /// The of this entity. + /// + public LiteralType LiteralType + { + get + { + return m_literalType; + } + } + + /// + /// Whether the last char read for this entity is a whitespace character. + /// + public bool IsWhitespace + { + get + { + return m_isWhitespace; + } + } + + /// + /// The proxy server to use when making web requests to resolve entities. + /// + public string Proxy + { + get + { + return m_proxy; + } + } + + /// + /// Reads the next character from the DTD stream. + /// + /// The next character from the DTD stream. + public char ReadChar() + { + char ch = (char)this.m_stm.Read(); + if (ch == 0) + { + // convert nulls to whitespace, since they are not valid in XML anyway. + ch = ' '; + } + this.m_absolutePos++; + if (ch == 0xa) + { + m_isWhitespace = true; + this.m_lineStart = this.m_absolutePos + 1; + this.m_line++; + } + else if (ch == ' ' || ch == '\t') + { + m_isWhitespace = true; + if (m_lastchar == 0xd) { - e = Encoding.GetEncoding(charset); + this.m_lineStart = this.m_absolutePos; + m_line++; } - catch (ArgumentException) - { - } - } } - - stream = resp.GetResponseStream(); - break; - } - - this.m_weOwnTheStream = true; - HtmlStream html = new HtmlStream(stream, e); - this.m_encoding = html.Encoding; - this.m_stm = html; - } - } - - private static HttpWebResponse GetResponse(HttpWebRequest wr, TimeSpan timeout) - { - HttpWebResponse response = null; - Exception exception = null; - var signal = new ManualResetEvent(false); - - wr.BeginGetResponse(ar => - { - try - { - response = (HttpWebResponse)wr.EndGetResponse(ar); - } - catch (Exception ex) - { - exception = ex; - } - finally - { - signal.Set(); - } - }, null); - - if (!signal.WaitOne(timeout)) - { - throw new TimeoutException("Timeout waiting for response"); - } - - if (exception != null) - { - throw new IOException(exception.Message, exception); - } - - return response; - } - - /// - /// Gets the character encoding for this entity. - /// - public Encoding Encoding - { - get - { - return this.m_encoding; - } - } - - /// - /// Closes the reader from which the entity is being read. - /// - public void Close() - { - if (this.m_weOwnTheStream) - this.m_stm.Dispose(); - } - - /// - /// Returns the next character after any whitespace. - /// - /// The next character that is not whitespace. - public char SkipWhitespace() - { - char ch = m_lastchar; - while (ch != Entity.EOF && (ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t')) - { - ch = ReadChar(); - } - return ch; - } - - /// - /// Scans a token from the input stream and returns the result. - /// - /// The to use to process the token. - /// A set of characters to look for as terminators for the token. - /// true if the token should be a NMToken, otherwise false. - /// The scanned token. - public string ScanToken(StringBuilder sb, string term, bool nmtoken) - { - if (sb == null) - throw new ArgumentNullException("sb"); - - if (term == null) - throw new ArgumentNullException("term"); - - sb.Length = 0; - char ch = m_lastchar; - if (nmtoken && ch != '_' && !char.IsLetter(ch)) - { - throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Invalid name start character '{0}'", ch)); - } - - while (ch != Entity.EOF && term.IndexOf(ch) < 0) - { - if (!nmtoken || ch == '_' || ch == '.' || ch == '-' || ch == ':' || char.IsLetterOrDigit(ch)) - { - sb.Append(ch); - } - else - { - throw new SgmlParseException( - string.Format(CultureInfo.CurrentUICulture, "Invalid name character '{0}'", ch)); - } - ch = ReadChar(); - } - - return sb.ToString(); - } - - /// - /// Read a literal from the input stream. - /// - /// The to use to build the literal. - /// The delimiter for the literal. - /// The literal scanned from the input stream. - public string ScanLiteral(StringBuilder sb, char quote) - { - if (sb == null) - throw new ArgumentNullException("sb"); - - sb.Length = 0; - char ch = ReadChar(); - while (ch != Entity.EOF && ch != quote) - { - if (ch == '&') - { - ch = ReadChar(); - if (ch == '#') - { - string charent = ExpandCharEntity(); - sb.Append(charent); - ch = this.m_lastchar; - } - else - { - sb.Append('&'); - sb.Append(ch); - ch = ReadChar(); - } - } - else - { - sb.Append(ch); - ch = ReadChar(); - } - } - - ReadChar(); // consume end quote. - return sb.ToString(); - } - - /// - /// Reads input until the end of the input stream or until a string of terminator characters is found. - /// - /// The to use to build the string. - /// The type of the element being read (only used in reporting errors). - /// The string of terminator characters to look for. - /// The string read from the input stream. - public string ScanToEnd(StringBuilder sb, string type, string terminators) - { - if (terminators == null) - throw new ArgumentNullException("terminators"); - - if (sb != null) - sb.Length = 0; - - int start = m_line; - // This method scans over a chunk of text looking for the - // termination sequence specified by the 'terminators' parameter. - char ch = ReadChar(); - int state = 0; - char next = terminators[state]; - while (ch != Entity.EOF) - { - if (ch == next) - { - state++; - if (state >= terminators.Length) - { - // found it! - break; - } - next = terminators[state]; - } - else if (state > 0) - { - // char didn't match, so go back and see how much does still match. - int i = state - 1; - int newstate = 0; - while (i >= 0 && newstate == 0) - { - if (terminators[i] == ch) + else if (ch == 0xd) { - // character is part of the terminators pattern, ok, so see if we can - // match all the way back to the beginning of the pattern. - int j = 1; - while (i - j >= 0) - { - if (terminators[i - j] != terminators[state - j]) - break; - - j++; - } - - if (j > i) - { - newstate = i + 1; - } + m_isWhitespace = true; } else { - i--; - } - } + m_isWhitespace = false; + if (m_lastchar == 0xd) + { + m_line++; + this.m_lineStart = this.m_absolutePos; + } + } - if (sb != null) - { - i = (i < 0) ? 1 : 0; - for (int k = 0; k <= state - newstate - i; k++) + m_lastchar = ch; + return ch; + } + + /// + /// Begins processing an entity. + /// + /// The parent of this entity. + /// The base Uri for processing this entity within. + public void Open(Entity parent, Uri baseUri) + { + this.m_parent = parent; + if (parent != null) + this.m_isHtml = parent.IsHtml; + this.m_line = 1; + if (m_isInternal) { - sb.Append(terminators[k]); - } - - if (i > 0) // see if we've matched this char or not - sb.Append(ch); // if not then append it to buffer. - } - - state = newstate; - next = terminators[newstate]; - } - else - { - if (sb != null) - sb.Append(ch); - } - - ch = ReadChar(); - } - - if (ch == 0) - Error(type + " starting on line {0} was never closed", start); - - ReadChar(); // consume last char in termination sequence. - if (sb != null) - return sb.ToString(); - else - return string.Empty; - } - - /// - /// Expands a character entity to be read from the input stream. - /// - /// The string for the character entity. - public string ExpandCharEntity() - { - string value; - int v = ReadNumericEntityCode(out value); - if (v == -1) - { - return value; - } - - // HACK ALERT: IE and Netscape map the unicode characters - if (this.m_isHtml && v >= 0x80 & v <= 0x9F) - { - // This range of control characters is mapped to Windows-1252! - int i = v - 0x80; - int unicode = CtrlMap[i]; - return Convert.ToChar(unicode).ToString(); - } - - if (0xD800 <= v && v <= 0xDBFF) - { - // high surrogate - if (m_lastchar == '&') - { - char ch = ReadChar(); - if (ch == '#') - { - string value2; - int v2 = ReadNumericEntityCode(out value2); - if (v2 == -1) + if (this.m_literal != null) + this.m_stm = new StringReader(this.m_literal); + } + else if (this.m_uri == null) { - return value + ";" + value2; + this.Error("Unresolvable entity '{0}'", this.m_name); } - if (0xDC00 <= v2 && v2 <= 0xDFFF) + else { + if (baseUri != null) + { + this.m_resolvedUri = new Uri(baseUri, this.m_uri); + } + else + { + this.m_resolvedUri = new Uri(this.m_uri); + } - // low surrogate - v = UTF32Utilities.ConvertToUtf32((char)v, (char)v2); + Stream stream = null; +#if PORTABLE + Encoding e = Encoding.UTF8; +#else + Encoding e = Encoding.Default; +#endif + switch (this.m_resolvedUri.Scheme) + { + case "file": + { +#if PORTABLE + throw new NotSupportedException("The file scheme is not supported by the .NETPortable framework."); +#else + string path = this.m_resolvedUri.LocalPath; + stream = new FileStream(path, FileMode.Open, FileAccess.Read); +#endif + } + break; + default: + //Console.WriteLine("Fetching:" + ResolvedUri.AbsoluteUri); + HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(ResolvedUri); +#if !PORTABLE + wr.UserAgent = "Mozilla/4.0 (compatible;);"; + wr.Timeout = 10000; // in case this is running in an ASPX page. + if (m_proxy != null) + wr.Proxy = new WebProxy(m_proxy); + wr.PreAuthenticate = false; + // Pass the credentials of the process. + wr.Credentials = CredentialCache.DefaultCredentials; +#endif + WebResponse resp = GetResponse(wr, TimeSpan.FromSeconds(10)); + Uri actual = resp.ResponseUri; + if (!string.Equals(actual.AbsoluteUri, this.m_resolvedUri.AbsoluteUri, StringComparison.OrdinalIgnoreCase)) + { + this.m_resolvedUri = actual; + } + string contentType = resp.ContentType.ToLowerInvariant(); + string mimeType = contentType; + int i = contentType.IndexOf(';'); + if (i >= 0) + { + mimeType = contentType.Substring(0, i); + } + + if (StringUtilities.EqualsIgnoreCase(mimeType, "text/html")) + { + this.m_isHtml = true; + } + + i = contentType.IndexOf("charset"); +#if PORTABLE + e = Encoding.UTF8; +#else + e = Encoding.Default; +#endif + + if (i >= 0) + { + int j = contentType.IndexOf("=", i); + int k = contentType.IndexOf(";", j); + if (k < 0) + k = contentType.Length; + + if (j > 0) + { + j++; + string charset = contentType.Substring(j, k - j).Trim(); + try + { + e = Encoding.GetEncoding(charset); + } + catch (ArgumentException) + { + } + } + } + + stream = resp.GetResponseStream(); + break; + } + + this.m_weOwnTheStream = true; + HtmlStream html = new HtmlStream(stream, e); + this.m_encoding = html.Encoding; + this.m_stm = html; } - } - else - { - Error("Premature {0} parsing surrogate pair", ch); - } } - else - { - Error("Premature {0} parsing surrogate pair", m_lastchar); - } - } - // NOTE (steveb): we need to use ConvertFromUtf32 to allow for extended numeric encodings - return UTF32Utilities.ConvertFromUtf32(v); - } + private static HttpWebResponse GetResponse(HttpWebRequest wr, TimeSpan timeout) + { + HttpWebResponse response = null; + Exception exception = null; + var signal = new ManualResetEvent(false); - private int ReadNumericEntityCode(out string value) - { - int v = 0; - char ch = ReadChar(); - value = "&#"; - if (ch == 'x') - { - bool sawHexDigit = false; - value += "x"; - ch = ReadChar(); - for (; ch != Entity.EOF && ch != ';'; ch = ReadChar()) - { - int p = 0; - if (ch >= '0' && ch <= '9') - { - p = (int)(ch - '0'); - sawHexDigit = true; - } - else if (ch >= 'a' && ch <= 'f') - { - p = (int)(ch - 'a') + 10; - sawHexDigit = true; - } - else if (ch >= 'A' && ch <= 'F') - { - p = (int)(ch - 'A') + 10; - sawHexDigit = true; - } - else - { - break; //we must be done! - //Error("Hex digit out of range '{0}'", (int)ch); - } - value += ch; - v = (v * 16) + p; - } - if (!sawHexDigit) - { - return -1; - } - } - else - { - bool sawDigit = false; - for (; ch != Entity.EOF && ch != ';'; ch = ReadChar()) - { - if (ch >= '0' && ch <= '9') - { - v = (v * 10) + (int)(ch - '0'); - sawDigit = true; - } - else - { - break; // we must be done! - //Error("Decimal digit out of range '{0}'", (int)ch); - } - value += ch; - } - if (!sawDigit) - { - return -1; - } - } - if (ch == 0) - { - Error("Premature {0} parsing entity reference", ch); - } - else if (ch == ';') - { - ReadChar(); - } - return v; - } + wr.BeginGetResponse(ar => + { + try + { + response = (HttpWebResponse) wr.EndGetResponse(ar); + } + catch (Exception ex) + { + exception = ex; + } + finally + { + signal.Set(); + } + }, null); - static int[] CtrlMap = new int[] { + if (!signal.WaitOne(timeout)) + { + throw new TimeoutException("Timeout waiting for response"); + } + + if (exception != null) + { + throw new IOException(exception.Message, exception); + } + + return response; + } + + /// + /// Gets the character encoding for this entity. + /// + public Encoding Encoding + { + get + { + return this.m_encoding; + } + } + + /// + /// Closes the reader from which the entity is being read. + /// + public void Close() + { + if (this.m_weOwnTheStream) + this.m_stm.Dispose(); + } + + /// + /// Returns the next character after any whitespace. + /// + /// The next character that is not whitespace. + public char SkipWhitespace() + { + char ch = m_lastchar; + while (ch != Entity.EOF && (ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t')) + { + ch = ReadChar(); + } + return ch; + } + + /// + /// Scans a token from the input stream and returns the result. + /// + /// The to use to process the token. + /// A set of characters to look for as terminators for the token. + /// true if the token should be a NMToken, otherwise false. + /// The scanned token. + public string ScanToken(StringBuilder sb, string term, bool nmtoken) + { + if (sb == null) + throw new ArgumentNullException("sb"); + + if (term == null) + throw new ArgumentNullException("term"); + + sb.Length = 0; + char ch = m_lastchar; + if (nmtoken && ch != '_' && !char.IsLetter(ch)) + { + throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Invalid name start character '{0}'", ch)); + } + + while (ch != Entity.EOF && term.IndexOf(ch) < 0) + { + if (!nmtoken || ch == '_' || ch == '.' || ch == '-' || ch == ':' || char.IsLetterOrDigit(ch)) { + sb.Append(ch); + } + else { + throw new SgmlParseException( + string.Format(CultureInfo.CurrentUICulture, "Invalid name character '{0}'", ch)); + } + ch = ReadChar(); + } + + return sb.ToString(); + } + + /// + /// Read a literal from the input stream. + /// + /// The to use to build the literal. + /// The delimiter for the literal. + /// The literal scanned from the input stream. + public string ScanLiteral(StringBuilder sb, char quote) + { + if (sb == null) + throw new ArgumentNullException("sb"); + + sb.Length = 0; + char ch = ReadChar(); + while (ch != Entity.EOF && ch != quote) + { + if (ch == '&') + { + ch = ReadChar(); + if (ch == '#') + { + string charent = ExpandCharEntity(); + sb.Append(charent); + ch = this.m_lastchar; + } + else + { + sb.Append('&'); + sb.Append(ch); + ch = ReadChar(); + } + } + else + { + sb.Append(ch); + ch = ReadChar(); + } + } + + ReadChar(); // consume end quote. + return sb.ToString(); + } + + /// + /// Reads input until the end of the input stream or until a string of terminator characters is found. + /// + /// The to use to build the string. + /// The type of the element being read (only used in reporting errors). + /// The string of terminator characters to look for. + /// The string read from the input stream. + public string ScanToEnd(StringBuilder sb, string type, string terminators) + { + if (terminators == null) + throw new ArgumentNullException("terminators"); + + if (sb != null) + sb.Length = 0; + + int start = m_line; + // This method scans over a chunk of text looking for the + // termination sequence specified by the 'terminators' parameter. + char ch = ReadChar(); + int state = 0; + char next = terminators[state]; + while (ch != Entity.EOF) + { + if (ch == next) + { + state++; + if (state >= terminators.Length) + { + // found it! + break; + } + next = terminators[state]; + } + else if (state > 0) + { + // char didn't match, so go back and see how much does still match. + int i = state - 1; + int newstate = 0; + while (i >= 0 && newstate == 0) + { + if (terminators[i] == ch) + { + // character is part of the terminators pattern, ok, so see if we can + // match all the way back to the beginning of the pattern. + int j = 1; + while (i - j >= 0) + { + if (terminators[i - j] != terminators[state - j]) + break; + + j++; + } + + if (j > i) + { + newstate = i + 1; + } + } + else + { + i--; + } + } + + if (sb != null) + { + i = (i < 0) ? 1 : 0; + for (int k = 0; k <= state - newstate - i; k++) + { + sb.Append(terminators[k]); + } + + if (i > 0) // see if we've matched this char or not + sb.Append(ch); // if not then append it to buffer. + } + + state = newstate; + next = terminators[newstate]; + } + else + { + if (sb != null) + sb.Append(ch); + } + + ch = ReadChar(); + } + + if (ch == 0) + Error(type + " starting on line {0} was never closed", start); + + ReadChar(); // consume last char in termination sequence. + if (sb != null) + return sb.ToString(); + else + return string.Empty; + } + + /// + /// Expands a character entity to be read from the input stream. + /// + /// The string for the character entity. + public string ExpandCharEntity() + { + string value; + int v = ReadNumericEntityCode(out value); + if(v == -1) + { + return value; + } + + // HACK ALERT: IE and Netscape map the unicode characters + if (this.m_isHtml && v >= 0x80 & v <= 0x9F) + { + // This range of control characters is mapped to Windows-1252! + int i = v - 0x80; + int unicode = CtrlMap[i]; + return Convert.ToChar(unicode).ToString(); + } + + if (0xD800 <= v && v <= 0xDBFF) + { + // high surrogate + if (m_lastchar == '&') + { + char ch = ReadChar(); + if (ch == '#') + { + string value2; + int v2 = ReadNumericEntityCode(out value2); + if(v2 == -1) + { + return value + ";" + value2; + } + if (0xDC00 <= v2 && v2 <= 0xDFFF) + { + + // low surrogate + v = UTF32Utilities.ConvertToUtf32((char)v, (char)v2); + } + } + else + { + Error("Premature {0} parsing surrogate pair", ch); + } + } + else + { + Error("Premature {0} parsing surrogate pair", m_lastchar); + } + } + + // NOTE (steveb): we need to use ConvertFromUtf32 to allow for extended numeric encodings + return UTF32Utilities.ConvertFromUtf32(v); + } + + private int ReadNumericEntityCode(out string value) + { + int v = 0; + char ch = ReadChar(); + value = "&#"; + if (ch == 'x') + { + bool sawHexDigit = false; + value += "x"; + ch = ReadChar(); + for (; ch != Entity.EOF && ch != ';'; ch = ReadChar()) + { + int p = 0; + if (ch >= '0' && ch <= '9') + { + p = (int)(ch - '0'); + sawHexDigit = true; + } + else if (ch >= 'a' && ch <= 'f') + { + p = (int)(ch - 'a') + 10; + sawHexDigit = true; + } + else if (ch >= 'A' && ch <= 'F') + { + p = (int)(ch - 'A') + 10; + sawHexDigit = true; + } + else + { + break; //we must be done! + //Error("Hex digit out of range '{0}'", (int)ch); + } + value += ch; + v = (v*16) + p; + } + if (!sawHexDigit) + { + return -1; + } + } + else + { + bool sawDigit = false; + for (; ch != Entity.EOF && ch != ';'; ch = ReadChar()) + { + if (ch >= '0' && ch <= '9') + { + v = (v*10) + (int)(ch - '0'); + sawDigit = true; + } + else + { + break; // we must be done! + //Error("Decimal digit out of range '{0}'", (int)ch); + } + value += ch; + } + if (!sawDigit) + { + return -1; + } + } + if (ch == 0) + { + Error("Premature {0} parsing entity reference", ch); + } + else if (ch == ';') + { + ReadChar(); + } + return v; + } + + static int[] CtrlMap = new int[] { // This is the windows-1252 mapping of the code points 0x80 through 0x9f. 8364, 129, 8218, 402, 8222, 8230, 8224, 8225, 710, 8240, 352, 8249, 338, 141, 381, 143, 144, 8216, 8217, 8220, 8221, 8226, 8211, 8212, 732, 8482, 353, 8250, 339, 157, 382, 376 }; - /// - /// Raise a processing error. - /// - /// The error message to use in the exception. - /// Always thrown. - public void Error(string msg) - { - throw new SgmlParseException(msg, this); - } - - /// - /// Raise a processing error. - /// - /// The error message to use in the exception. - /// The unexpected character causing the error. - /// Always thrown. - public void Error(string msg, char ch) - { - string str = (ch == Entity.EOF) ? "EOF" : char.ToString(ch); - throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, msg, str), this); - } - - /// - /// Raise a processing error. - /// - /// The error message to use in the exception. - /// The value causing the error. - /// Always thrown. - public void Error(string msg, int x) - { - throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, msg, x), this); - } - - /// - /// Raise a processing error. - /// - /// The error message to use in the exception. - /// The argument for the error. - /// Always thrown. - public void Error(string msg, string arg) - { - throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, msg, arg), this); - } - - /// - /// Returns a string giving information on how the entity is referenced and declared, walking up the parents until the top level parent entity is found. - /// - /// Contextual information for the entity. - public string Context() - { - Entity p = this; - StringBuilder sb = new StringBuilder(); - while (p != null) - { - string msg; - if (p.m_isInternal) + /// + /// Raise a processing error. + /// + /// The error message to use in the exception. + /// Always thrown. + public void Error(string msg) { - msg = string.Format(CultureInfo.InvariantCulture, "\nReferenced on line {0}, position {1} of internal entity '{2}'", p.m_line, p.LinePosition, p.m_name); + throw new SgmlParseException(msg, this); } - else + + /// + /// Raise a processing error. + /// + /// The error message to use in the exception. + /// The unexpected character causing the error. + /// Always thrown. + public void Error(string msg, char ch) { - msg = string.Format(CultureInfo.InvariantCulture, "\nReferenced on line {0}, position {1} of '{2}' entity at [{3}]", p.m_line, p.LinePosition, p.m_name, p.ResolvedUri.AbsolutePath); + string str = (ch == Entity.EOF) ? "EOF" : char.ToString(ch); + throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, msg, str), this); } - sb.Append(msg); - p = p.Parent; - } - return sb.ToString(); - } - - /// - /// Checks whether a token denotes a literal entity or not. - /// - /// The token to check. - /// true if the token is "CDATA", "SDATA" or "PI", otherwise false. - public static bool IsLiteralType(string token) - { - return string.Equals(token, "CDATA", StringComparison.OrdinalIgnoreCase) || - string.Equals(token, "SDATA", StringComparison.OrdinalIgnoreCase) || - string.Equals(token, "PI", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Sets the entity to be a literal of the type specified. - /// - /// One of "CDATA", "SDATA" or "PI". - public void SetLiteralType(string token) - { - switch (token) - { - case "CDATA": - this.m_literalType = LiteralType.CDATA; - break; - case "SDATA": - this.m_literalType = LiteralType.SDATA; - break; - case "PI": - this.m_literalType = LiteralType.PI; - break; - } - } - - #region IDisposable Members - - /// - /// The finalizer for the Entity class. - /// - ~Entity() - { - Dispose(false); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// true if this method has been called by user code, false if it has been called through a finalizer. - protected virtual void Dispose(bool isDisposing) - { - if (isDisposing) - { - if (m_stm != null) + /// + /// Raise a processing error. + /// + /// The error message to use in the exception. + /// The value causing the error. + /// Always thrown. + public void Error(string msg, int x) { - m_stm.Dispose(); - m_stm = null; + throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, msg, x), this); } - } - } - #endregion - } - - // This class decodes an HTML/XML stream correctly. - internal class HtmlStream : TextReader - { - private Stream stm; - private byte[] rawBuffer; - private int rawPos; - private int rawUsed; - private Encoding m_encoding; - private Decoder m_decoder; - private char[] m_buffer; - private int used; - private int pos; - private const int BUFSIZE = 16384; - private const int EOF = -1; - - public HtmlStream(Stream stm, Encoding defaultEncoding) - { - if (defaultEncoding == null) defaultEncoding = Encoding.UTF8; // default is UTF8 - if (!stm.CanSeek) - { - // Need to be able to seek to sniff correctly. - stm = CopyToMemoryStream(stm); - } - this.stm = stm; - rawBuffer = new Byte[BUFSIZE]; - rawUsed = stm.Read(rawBuffer, 0, 4); // maximum byte order mark - this.m_buffer = new char[BUFSIZE]; - - // Check byte order marks - this.m_decoder = AutoDetectEncoding(rawBuffer, ref rawPos, rawUsed); - int bom = rawPos; - if (this.m_decoder == null) - { - this.m_decoder = defaultEncoding.GetDecoder(); - rawUsed += stm.Read(rawBuffer, 4, BUFSIZE - 4); - DecodeBlock(); - // Now sniff to see if there is an XML declaration or HTML tag. - Decoder sd = SniffEncoding(); - if (sd != null) + /// + /// Raise a processing error. + /// + /// The error message to use in the exception. + /// The argument for the error. + /// Always thrown. + public void Error(string msg, string arg) { - this.m_decoder = sd; + throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, msg, arg), this); } - } - // Reset to get ready for Read() - this.stm.Seek(0, SeekOrigin.Begin); - this.pos = this.used = 0; - // skip bom - if (bom > 0) - { - stm.Read(this.rawBuffer, 0, bom); - } - this.rawPos = this.rawUsed = 0; - - } - - public Encoding Encoding - { - get - { - return this.m_encoding; - } - } - - private static Stream CopyToMemoryStream(Stream s) - { - int size = 100000; // large heap is more efficient - byte[] copyBuff = new byte[size]; - int len; - MemoryStream r = new MemoryStream(); - while ((len = s.Read(copyBuff, 0, size)) > 0) - r.Write(copyBuff, 0, len); - - r.Seek(0, SeekOrigin.Begin); - s.Dispose(); - return r; - } - - internal void DecodeBlock() - { - // shift current chars to beginning. - if (pos > 0) - { - if (pos < used) + /// + /// Returns a string giving information on how the entity is referenced and declared, walking up the parents until the top level parent entity is found. + /// + /// Contextual information for the entity. + public string Context() { - System.Array.Copy(m_buffer, pos, m_buffer, 0, used - pos); - } - used -= pos; - pos = 0; - } - int len = m_decoder.GetCharCount(rawBuffer, rawPos, rawUsed - rawPos); - int available = m_buffer.Length - used; - if (available < len) - { - char[] newbuf = new char[m_buffer.Length + len]; - System.Array.Copy(m_buffer, pos, newbuf, 0, used - pos); - m_buffer = newbuf; - } - used = pos + m_decoder.GetChars(rawBuffer, rawPos, rawUsed - rawPos, m_buffer, pos); - rawPos = rawUsed; // consumed the whole buffer! - } - internal static Decoder AutoDetectEncoding(byte[] buffer, ref int index, int length) - { - if (4 <= (length - index)) - { - uint w = (uint)buffer[index + 0] << 24 | (uint)buffer[index + 1] << 16 | (uint)buffer[index + 2] << 8 | (uint)buffer[index + 3]; - // see if it's a 4-byte encoding - switch (w) - { - case 0xfefffeff: - index += 4; - return new Ucs4DecoderBigEngian(); - - case 0xfffefffe: - index += 4; - return new Ucs4DecoderLittleEndian(); - - case 0x3c000000: - goto case 0xfefffeff; - - case 0x0000003c: - goto case 0xfffefffe; - } - w >>= 8; - if (w == 0xefbbbf) - { - index += 3; - return Encoding.UTF8.GetDecoder(); - } - w >>= 8; - switch (w) - { - case 0xfeff: - index += 2; - return UnicodeEncoding.BigEndianUnicode.GetDecoder(); - - case 0xfffe: - index += 2; - return new UnicodeEncoding(false, false).GetDecoder(); - - case 0x3c00: - goto case 0xfeff; - - case 0x003c: - goto case 0xfffe; - } - } - return null; - } - private int ReadChar() - { - // Read only up to end of current buffer then stop. - if (pos < used) return m_buffer[pos++]; - return EOF; - } - private int PeekChar() - { - int ch = ReadChar(); - if (ch != EOF) - { - pos--; - } - return ch; - } - private bool SniffPattern(string pattern) - { - int ch = PeekChar(); - if (ch != pattern[0]) return false; - for (int i = 0, n = pattern.Length; ch != EOF && i < n; i++) - { - ch = ReadChar(); - char m = pattern[i]; - if (ch != m) - { - return false; - } - } - return true; - } - private void SniffWhitespace() - { - char ch = (char)PeekChar(); - while (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') - { - int i = pos; - ch = (char)ReadChar(); - if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') - pos = i; - } - } - - private string SniffLiteral() - { - int quoteChar = PeekChar(); - if (quoteChar == '\'' || quoteChar == '"') - { - ReadChar();// consume quote char - int i = this.pos; - int ch = ReadChar(); - while (ch != EOF && ch != quoteChar) - { - ch = ReadChar(); - } - return (pos > i) ? new string(m_buffer, i, pos - i - 1) : ""; - } - return null; - } - private string SniffAttribute(string name) - { - SniffWhitespace(); - string id = SniffName(); - if (string.Equals(name, id, StringComparison.OrdinalIgnoreCase)) - { - SniffWhitespace(); - if (SniffPattern("=")) - { - SniffWhitespace(); - return SniffLiteral(); - } - } - return null; - } - private string SniffAttribute(out string name) - { - SniffWhitespace(); - name = SniffName(); - if (name != null) - { - SniffWhitespace(); - if (SniffPattern("=")) - { - SniffWhitespace(); - return SniffLiteral(); - } - } - return null; - } - private void SniffTerminator(string term) - { - int ch = ReadChar(); - int i = 0; - int n = term.Length; - while (i < n && ch != EOF) - { - if (term[i] == ch) - { - i++; - if (i == n) break; - } - else - { - i = 0; // reset. - } - ch = ReadChar(); - } - } - - internal Decoder SniffEncoding() - { - Decoder decoder = null; - if (SniffPattern(""); - } - } - if (decoder == null) - { - return SniffMeta(); - } - return null; - } - - internal Decoder SniffMeta() - { - int i = ReadChar(); - while (i != EOF) - { - char ch = (char)i; - if (ch == '<') - { - string name = SniffName(); - if (name != null && StringUtilities.EqualsIgnoreCase(name, "meta")) - { - string httpequiv = null; - string content = null; - while (true) - { - string value = SniffAttribute(out name); - if (name == null) - break; - - if (StringUtilities.EqualsIgnoreCase(name, "http-equiv")) - { - httpequiv = value; - } - else if (StringUtilities.EqualsIgnoreCase(name, "content")) - { - content = value; - } - } - - if (httpequiv != null && StringUtilities.EqualsIgnoreCase(httpequiv, "content-type") && content != null) - { - int j = content.IndexOf("charset"); - if (j >= 0) - { - //charset=utf-8 - j = content.IndexOf("=", j); - if (j >= 0) + string msg; + if (p.m_isInternal) { - j++; - int k = content.IndexOf(";", j); - if (k < 0) k = content.Length; - string charset = content.Substring(j, k - j).Trim(); - try - { - Encoding e = Encoding.GetEncoding(charset); - this.m_encoding = e; - return e.GetDecoder(); - } - catch (ArgumentException) { } + msg = string.Format(CultureInfo.InvariantCulture, "\nReferenced on line {0}, position {1} of internal entity '{2}'", p.m_line, p.LinePosition, p.m_name); + } + else { + msg = string.Format(CultureInfo.InvariantCulture, "\nReferenced on line {0}, position {1} of '{2}' entity at [{3}]", p.m_line, p.LinePosition, p.m_name, p.ResolvedUri.AbsolutePath); } - } + sb.Append(msg); + p = p.Parent; } - } + + return sb.ToString(); } - i = ReadChar(); - } - return null; - } - - internal string SniffName() - { - int c = PeekChar(); - if (c == EOF) - return null; - char ch = (char)c; - int start = pos; - while (pos < used - 1 && (char.IsLetterOrDigit(ch) || ch == '-' || ch == '_' || ch == ':')) - ch = m_buffer[++pos]; - - if (start == pos) - return null; - - return new string(m_buffer, start, pos - start); - } - - [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] - internal void SkipWhitespace() - { - char ch = (char)PeekChar(); - while (pos < used - 1 && (ch == ' ' || ch == '\r' || ch == '\n')) - ch = m_buffer[++pos]; - } - - [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] - internal void SkipTo(char what) - { - char ch = (char)PeekChar(); - while (pos < used - 1 && (ch != what)) - ch = m_buffer[++pos]; - } - - [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] - internal string ParseAttribute() - { - SkipTo('='); - if (pos < used) - { - pos++; - SkipWhitespace(); - if (pos < used) + /// + /// Checks whether a token denotes a literal entity or not. + /// + /// The token to check. + /// true if the token is "CDATA", "SDATA" or "PI", otherwise false. + public static bool IsLiteralType(string token) { - char quote = m_buffer[pos]; - pos++; - int start = pos; - SkipTo(quote); - if (pos < used) - { - string result = new string(m_buffer, start, pos - start); - pos++; + return string.Equals(token, "CDATA", StringComparison.OrdinalIgnoreCase) || + string.Equals(token, "SDATA", StringComparison.OrdinalIgnoreCase) || + string.Equals(token, "PI", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Sets the entity to be a literal of the type specified. + /// + /// One of "CDATA", "SDATA" or "PI". + public void SetLiteralType(string token) + { + switch (token) + { + case "CDATA": + this.m_literalType = LiteralType.CDATA; + break; + case "SDATA": + this.m_literalType = LiteralType.SDATA; + break; + case "PI": + this.m_literalType = LiteralType.PI; + break; + } + } + + #region IDisposable Members + + /// + /// The finalizer for the Entity class. + /// + ~Entity() + { + Dispose(false); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// true if this method has been called by user code, false if it has been called through a finalizer. + protected virtual void Dispose(bool isDisposing) + { + if (isDisposing) + { + if (m_stm != null) + { + m_stm.Dispose(); + m_stm = null; + } + } + } + + #endregion + } + + // This class decodes an HTML/XML stream correctly. + internal class HtmlStream : TextReader + { + private Stream stm; + private byte[] rawBuffer; + private int rawPos; + private int rawUsed; + private Encoding m_encoding; + private Decoder m_decoder; + private char[] m_buffer; + private int used; + private int pos; + private const int BUFSIZE = 16384; + private const int EOF = -1; + + public HtmlStream(Stream stm, Encoding defaultEncoding) + { + if (defaultEncoding == null) defaultEncoding = Encoding.UTF8; // default is UTF8 + if (!stm.CanSeek){ + // Need to be able to seek to sniff correctly. + stm = CopyToMemoryStream(stm); + } + this.stm = stm; + rawBuffer = new Byte[BUFSIZE]; + rawUsed = stm.Read(rawBuffer, 0, 4); // maximum byte order mark + this.m_buffer = new char[BUFSIZE]; + + // Check byte order marks + this.m_decoder = AutoDetectEncoding(rawBuffer, ref rawPos, rawUsed); + int bom = rawPos; + if (this.m_decoder == null) + { + this.m_decoder = defaultEncoding.GetDecoder(); + rawUsed += stm.Read(rawBuffer, 4, BUFSIZE-4); + DecodeBlock(); + // Now sniff to see if there is an XML declaration or HTML tag. + Decoder sd = SniffEncoding(); + if (sd != null) { + this.m_decoder = sd; + } + } + + // Reset to get ready for Read() + this.stm.Seek(0, SeekOrigin.Begin); + this.pos = this.used = 0; + // skip bom + if (bom>0){ + stm.Read(this.rawBuffer, 0, bom); + } + this.rawPos = this.rawUsed = 0; + + } + + public Encoding Encoding + { + get + { + return this.m_encoding; + } + } + + private static Stream CopyToMemoryStream(Stream s) + { + int size = 100000; // large heap is more efficient + byte[] copyBuff = new byte[size]; + int len; + MemoryStream r = new MemoryStream(); + while ((len = s.Read(copyBuff, 0, size)) > 0) + r.Write(copyBuff, 0, len); + + r.Seek(0, SeekOrigin.Begin); + s.Dispose(); + return r; + } + + internal void DecodeBlock() { + // shift current chars to beginning. + if (pos > 0) { + if (pos < used) { + System.Array.Copy(m_buffer, pos, m_buffer, 0, used - pos); + } + used -= pos; + pos = 0; + } + int len = m_decoder.GetCharCount(rawBuffer, rawPos, rawUsed - rawPos); + int available = m_buffer.Length - used; + if (available < len) { + char[] newbuf = new char[m_buffer.Length + len]; + System.Array.Copy(m_buffer, pos, newbuf, 0, used - pos); + m_buffer = newbuf; + } + used = pos + m_decoder.GetChars(rawBuffer, rawPos, rawUsed - rawPos, m_buffer, pos); + rawPos = rawUsed; // consumed the whole buffer! + } + internal static Decoder AutoDetectEncoding(byte[] buffer, ref int index, int length) { + if (4 <= (length - index)) { + uint w = (uint)buffer[index + 0] << 24 | (uint)buffer[index + 1] << 16 | (uint)buffer[index + 2] << 8 | (uint)buffer[index + 3]; + // see if it's a 4-byte encoding + switch (w) { + case 0xfefffeff: + index += 4; + return new Ucs4DecoderBigEngian(); + + case 0xfffefffe: + index += 4; + return new Ucs4DecoderLittleEndian(); + + case 0x3c000000: + goto case 0xfefffeff; + + case 0x0000003c: + goto case 0xfffefffe; + } + w >>= 8; + if (w == 0xefbbbf) { + index += 3; + return Encoding.UTF8.GetDecoder(); + } + w >>= 8; + switch (w) { + case 0xfeff: + index += 2; + return UnicodeEncoding.BigEndianUnicode.GetDecoder(); + + case 0xfffe: + index += 2; + return new UnicodeEncoding(false, false).GetDecoder(); + + case 0x3c00: + goto case 0xfeff; + + case 0x003c: + goto case 0xfffe; + } + } + return null; + } + private int ReadChar() { + // Read only up to end of current buffer then stop. + if (pos < used) return m_buffer[pos++]; + return EOF; + } + private int PeekChar() { + int ch = ReadChar(); + if (ch != EOF) { + pos--; + } + return ch; + } + private bool SniffPattern(string pattern) { + int ch = PeekChar(); + if (ch != pattern[0]) return false; + for (int i = 0, n = pattern.Length; ch != EOF && i < n; i++) { + ch = ReadChar(); + char m = pattern[i]; + if (ch != m) { + return false; + } + } + return true; + } + private void SniffWhitespace() { + char ch = (char)PeekChar(); + while (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { + int i = pos; + ch = (char)ReadChar(); + if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') + pos = i; + } + } + + private string SniffLiteral() { + int quoteChar = PeekChar(); + if (quoteChar == '\'' || quoteChar == '"') { + ReadChar();// consume quote char + int i = this.pos; + int ch = ReadChar(); + while (ch != EOF && ch != quoteChar) { + ch = ReadChar(); + } + return (pos>i) ? new string(m_buffer, i, pos - i - 1) : ""; + } + return null; + } + private string SniffAttribute(string name) { + SniffWhitespace(); + string id = SniffName(); + if (string.Equals(name, id, StringComparison.OrdinalIgnoreCase)) { + SniffWhitespace(); + if (SniffPattern("=")) { + SniffWhitespace(); + return SniffLiteral(); + } + } + return null; + } + private string SniffAttribute(out string name) { + SniffWhitespace(); + name = SniffName(); + if (name != null){ + SniffWhitespace(); + if (SniffPattern("=")) { + SniffWhitespace(); + return SniffLiteral(); + } + } + return null; + } + private void SniffTerminator(string term) { + int ch = ReadChar(); + int i = 0; + int n = term.Length; + while (i < n && ch != EOF) { + if (term[i] == ch) { + i++; + if (i == n) break; + } else { + i = 0; // reset. + } + ch = ReadChar(); + } + } + + internal Decoder SniffEncoding() + { + Decoder decoder = null; + if (SniffPattern(""); + } + } + if (decoder == null) { + return SniffMeta(); + } + return null; + } + + internal Decoder SniffMeta() + { + int i = ReadChar(); + while (i != EOF) + { + char ch = (char)i; + if (ch == '<') + { + string name = SniffName(); + if (name != null && StringUtilities.EqualsIgnoreCase(name, "meta")) + { + string httpequiv = null; + string content = null; + while (true) + { + string value = SniffAttribute(out name); + if (name == null) + break; + + if (StringUtilities.EqualsIgnoreCase(name, "http-equiv")) + { + httpequiv = value; + } + else if (StringUtilities.EqualsIgnoreCase(name, "content")) + { + content = value; + } + } + + if (httpequiv != null && StringUtilities.EqualsIgnoreCase(httpequiv, "content-type") && content != null) + { + int j = content.IndexOf("charset"); + if (j >= 0) + { + //charset=utf-8 + j = content.IndexOf("=", j); + if (j >= 0) + { + j++; + int k = content.IndexOf(";", j); + if (k<0) k = content.Length; + string charset = content.Substring(j, k-j).Trim(); + try + { + Encoding e = Encoding.GetEncoding(charset); + this.m_encoding = e; + return e.GetDecoder(); + } catch (ArgumentException) {} + } + } + } + } + } + i = ReadChar(); + + } + return null; + } + + internal string SniffName() + { + int c = PeekChar(); + if (c == EOF) + return null; + char ch = (char)c; + int start = pos; + while (pos < used - 1 && (char.IsLetterOrDigit(ch) || ch == '-' || ch == '_' || ch == ':')) + ch = m_buffer[++pos]; + + if (start == pos) + return null; + + return new string(m_buffer, start, pos - start); + } + + [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] + internal void SkipWhitespace() + { + char ch = (char)PeekChar(); + while (pos < used - 1 && (ch == ' ' || ch == '\r' || ch == '\n')) + ch = m_buffer[++pos]; + } + + [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] + internal void SkipTo(char what) + { + char ch = (char)PeekChar(); + while (pos < used - 1 && (ch != what)) + ch = m_buffer[++pos]; + } + + [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] + internal string ParseAttribute() + { + SkipTo('='); + if (pos < used) + { + pos++; + SkipWhitespace(); + if (pos < used) { + char quote = m_buffer[pos]; + pos++; + int start = pos; + SkipTo(quote); + if (pos < used) { + string result = new string(m_buffer, start, pos - start); + pos++; + return result; + } + } + } + return null; + } + public override int Peek() { + int result = Read(); + if (result != EOF) { + pos--; + } return result; - } } - } - return null; - } - public override int Peek() - { - int result = Read(); - if (result != EOF) - { - pos--; - } - return result; - } - public override int Read() - { - if (pos == used) - { - rawUsed = stm.Read(rawBuffer, 0, rawBuffer.Length); - rawPos = 0; - if (rawUsed == 0) return EOF; - DecodeBlock(); - } - if (pos < used) return m_buffer[pos++]; - return -1; - } - - public override int Read(char[] buffer, int start, int length) - { - if (pos == used) - { - rawUsed = stm.Read(rawBuffer, 0, rawBuffer.Length); - rawPos = 0; - if (rawUsed == 0) return -1; - DecodeBlock(); - } - if (pos < used) - { - length = Math.Min(used - pos, length); - Array.Copy(this.m_buffer, pos, buffer, start, length); - pos += length; - return length; - } - return 0; - } - - public override int ReadBlock(char[] data, int index, int count) - { - return Read(data, index, count); - } - - // Read up to end of line, or full buffer, whichever comes first. - [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] - public int ReadLine(char[] buffer, int start, int length) - { - int i = 0; - int ch = ReadChar(); - while (ch != EOF) - { - buffer[i + start] = (char)ch; - i++; - if (i + start == length) - break; // buffer is full - - if (ch == '\r') + public override int Read() { - if (PeekChar() == '\n') - { - ch = ReadChar(); - buffer[i + start] = (char)ch; - i++; - } - break; + if (pos == used) + { + rawUsed = stm.Read(rawBuffer, 0, rawBuffer.Length); + rawPos = 0; + if (rawUsed == 0) return EOF; + DecodeBlock(); + } + if (pos < used) return m_buffer[pos++]; + return -1; } - else if (ch == '\n') + + public override int Read(char[] buffer, int start, int length) { + if (pos == used) { + rawUsed = stm.Read(rawBuffer, 0, rawBuffer.Length); + rawPos = 0; + if (rawUsed == 0) return -1; + DecodeBlock(); + } + if (pos < used) { + length = Math.Min(used - pos, length); + Array.Copy(this.m_buffer, pos, buffer, start, length); + pos += length; + return length; + } + return 0; + } + + public override int ReadBlock(char[] data, int index, int count) { - break; + return Read(data, index, count); + } + + // Read up to end of line, or full buffer, whichever comes first. + [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] + public int ReadLine(char[] buffer, int start, int length) + { + int i = 0; + int ch = ReadChar(); + while (ch != EOF) { + buffer[i+start] = (char)ch; + i++; + if (i+start == length) + break; // buffer is full + + if (ch == '\r' ) { + if (PeekChar() == '\n') { + ch = ReadChar(); + buffer[i + start] = (char)ch; + i++; + } + break; + } else if (ch == '\n') { + break; + } + ch = ReadChar(); + } + return i; + } + + public override string ReadToEnd() { + char[] buffer = new char[100000]; // large block heap is more efficient + int len = 0; + StringBuilder sb = new StringBuilder(); + while ((len = Read(buffer, 0, buffer.Length)) > 0) { + sb.Append(buffer, 0, len); + } + return sb.ToString(); + } + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + { + stm.Dispose(); + } } - ch = ReadChar(); - } - return i; } - public override string ReadToEnd() - { - char[] buffer = new char[100000]; // large block heap is more efficient - int len = 0; - StringBuilder sb = new StringBuilder(); - while ((len = Read(buffer, 0, buffer.Length)) > 0) - { - sb.Append(buffer, 0, len); - } - return sb.ToString(); + internal abstract class Ucs4Decoder : Decoder { + internal byte[] temp = new byte[4]; + internal int tempBytes = 0; + public override int GetCharCount(byte[] bytes, int index, int count) { + return (count + tempBytes) / 4; + } + internal abstract int GetFullChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex); + public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) { + int i = tempBytes; + + if (tempBytes > 0) { + for (; i < 4; i++) { + temp[i] = bytes[byteIndex]; + byteIndex++; + byteCount--; + } + i = 1; + GetFullChars(temp, 0, 4, chars, charIndex); + charIndex++; + } else + i = 0; + i = GetFullChars(bytes, byteIndex, byteCount, chars, charIndex) + i; + + int j = (tempBytes + byteCount) % 4; + byteCount += byteIndex; + byteIndex = byteCount - j; + tempBytes = 0; + + if (byteIndex >= 0) + for (; byteIndex < byteCount; byteIndex++) { + temp[tempBytes] = bytes[byteIndex]; + tempBytes++; + } + return i; + } + internal static char UnicodeToUTF16(UInt32 code) { + byte lowerByte, higherByte; + lowerByte = (byte)(0xD7C0 + (code >> 10)); + higherByte = (byte)(0xDC00 | code & 0x3ff); + return ((char)((higherByte << 8) | lowerByte)); + } } - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - stm.Dispose(); - } + + internal class Ucs4DecoderBigEngian : Ucs4Decoder { + internal override int GetFullChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) { + UInt32 code; + int i, j; + byteCount += byteIndex; + for (i = byteIndex, j = charIndex; i + 3 < byteCount; ) { + code = (UInt32)(((bytes[i + 3]) << 24) | (bytes[i + 2] << 16) | (bytes[i + 1] << 8) | (bytes[i])); + if (code > 0x10FFFF) { + throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Invalid character 0x{0:x} in encoding", code)); + } else if (code > 0xFFFF) { + chars[j] = UnicodeToUTF16(code); + j++; + } else { + if (code >= 0xD800 && code <= 0xDFFF) { + throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Invalid character 0x{0:x} in encoding", code)); + } else { + chars[j] = (char)code; + } + } + j++; + i += 4; + } + return j - charIndex; + } } - } - internal abstract class Ucs4Decoder : Decoder - { - internal byte[] temp = new byte[4]; - internal int tempBytes = 0; - public override int GetCharCount(byte[] bytes, int index, int count) - { - return (count + tempBytes) / 4; - } - internal abstract int GetFullChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex); - public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) - { - int i = tempBytes; - - if (tempBytes > 0) - { - for (; i < 4; i++) - { - temp[i] = bytes[byteIndex]; - byteIndex++; - byteCount--; + internal class Ucs4DecoderLittleEndian : Ucs4Decoder { + internal override int GetFullChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) { + UInt32 code; + int i, j; + byteCount += byteIndex; + for (i = byteIndex, j = charIndex; i + 3 < byteCount; ) { + code = (UInt32)(((bytes[i]) << 24) | (bytes[i + 1] << 16) | (bytes[i + 2] << 8) | (bytes[i + 3])); + if (code > 0x10FFFF) { + throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Invalid character 0x{0:x} in encoding", code)); + } else if (code > 0xFFFF) { + chars[j] = UnicodeToUTF16(code); + j++; + } else { + if (code >= 0xD800 && code <= 0xDFFF) { + throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Invalid character 0x{0:x} in encoding", code)); + } else { + chars[j] = (char)code; + } + } + j++; + i += 4; + } + return j - charIndex; } - i = 1; - GetFullChars(temp, 0, 4, chars, charIndex); - charIndex++; - } - else - i = 0; - i = GetFullChars(bytes, byteIndex, byteCount, chars, charIndex) + i; - - int j = (tempBytes + byteCount) % 4; - byteCount += byteIndex; - byteIndex = byteCount - j; - tempBytes = 0; - - if (byteIndex >= 0) - for (; byteIndex < byteCount; byteIndex++) - { - temp[tempBytes] = bytes[byteIndex]; - tempBytes++; - } - return i; - } - internal static char UnicodeToUTF16(UInt32 code) - { - byte lowerByte, higherByte; - lowerByte = (byte)(0xD7C0 + (code >> 10)); - higherByte = (byte)(0xDC00 | code & 0x3ff); - return ((char)((higherByte << 8) | lowerByte)); - } - } - - internal class Ucs4DecoderBigEngian : Ucs4Decoder - { - internal override int GetFullChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) - { - UInt32 code; - int i, j; - byteCount += byteIndex; - for (i = byteIndex, j = charIndex; i + 3 < byteCount; ) - { - code = (UInt32)(((bytes[i + 3]) << 24) | (bytes[i + 2] << 16) | (bytes[i + 1] << 8) | (bytes[i])); - if (code > 0x10FFFF) - { - throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Invalid character 0x{0:x} in encoding", code)); - } - else if (code > 0xFFFF) - { - chars[j] = UnicodeToUTF16(code); - j++; - } - else - { - if (code >= 0xD800 && code <= 0xDFFF) - { - throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Invalid character 0x{0:x} in encoding", code)); - } - else - { - chars[j] = (char)code; - } - } - j++; - i += 4; - } - return j - charIndex; - } - } - - internal class Ucs4DecoderLittleEndian : Ucs4Decoder - { - internal override int GetFullChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) - { - UInt32 code; - int i, j; - byteCount += byteIndex; - for (i = byteIndex, j = charIndex; i + 3 < byteCount; ) - { - code = (UInt32)(((bytes[i]) << 24) | (bytes[i + 1] << 16) | (bytes[i + 2] << 8) | (bytes[i + 3])); - if (code > 0x10FFFF) - { - throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Invalid character 0x{0:x} in encoding", code)); - } - else if (code > 0xFFFF) - { - chars[j] = UnicodeToUTF16(code); - j++; - } - else - { - if (code >= 0xD800 && code <= 0xDFFF) - { - throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Invalid character 0x{0:x} in encoding", code)); - } - else - { - chars[j] = (char)code; - } - } - j++; - i += 4; - } - return j - charIndex; - } - } - - /// - /// An element declaration in a DTD. - /// - public class ElementDecl - { - private string m_name; - private bool m_startTagOptional; - private bool m_endTagOptional; - private ContentModel m_contentModel; - private string[] m_inclusions; - private string[] m_exclusions; - private Dictionary m_attList; - - /// - /// Initialises a new element declaration instance. - /// - /// The name of the element. - /// Whether the start tag is optional. - /// Whether the end tag is optional. - /// The of the element. - /// - /// - public ElementDecl(string name, bool sto, bool eto, ContentModel cm, string[] inclusions, string[] exclusions) - { - m_name = name; - m_startTagOptional = sto; - m_endTagOptional = eto; - m_contentModel = cm; - m_inclusions = inclusions; - m_exclusions = exclusions; } /// - /// The element name. + /// An element declaration in a DTD. /// - public string Name + public class ElementDecl { - get - { - return m_name; - } - } + private string m_name; + private bool m_startTagOptional; + private bool m_endTagOptional; + private ContentModel m_contentModel; + private string[] m_inclusions; + private string[] m_exclusions; + private Dictionary m_attList; - /// - /// The of the element declaration. - /// - public ContentModel ContentModel - { - get - { - return m_contentModel; - } - } - - /// - /// Whether the end tag of the element is optional. - /// - /// true if the end tag of the element is optional, otherwise false. - public bool EndTagOptional - { - get - { - return m_endTagOptional; - } - } - - /// - /// Whether the start tag of the element is optional. - /// - /// true if the start tag of the element is optional, otherwise false. - public bool StartTagOptional - { - get - { - return m_startTagOptional; - } - } - - /// - /// Finds the attribute definition with the specified name. - /// - /// The name of the to find. - /// The with the specified name. - /// If the attribute list has not yet been initialised. - public AttDef FindAttribute(string name) - { - if (m_attList == null) - throw new InvalidOperationException("The attribute list for the element declaration has not been initialised."); - - AttDef a; - m_attList.TryGetValue(name.ToUpperInvariant(), out a); - return a; - } - - /// - /// Adds attribute definitions to the element declaration. - /// - /// The list of attribute definitions to add. - public void AddAttDefs(Dictionary list) - { - if (list == null) - throw new ArgumentNullException("list"); - - if (m_attList == null) - { - m_attList = list; - } - else - { - foreach (AttDef a in list.Values) + /// + /// Initialises a new element declaration instance. + /// + /// The name of the element. + /// Whether the start tag is optional. + /// Whether the end tag is optional. + /// The of the element. + /// + /// + public ElementDecl(string name, bool sto, bool eto, ContentModel cm, string[] inclusions, string[] exclusions) { - if (!m_attList.ContainsKey(a.Name)) - { - m_attList.Add(a.Name, a); - } + m_name = name; + m_startTagOptional = sto; + m_endTagOptional = eto; + m_contentModel = cm; + m_inclusions = inclusions; + m_exclusions = exclusions; + } + + /// + /// The element name. + /// + public string Name + { + get + { + return m_name; + } + } + + /// + /// The of the element declaration. + /// + public ContentModel ContentModel + { + get + { + return m_contentModel; + } + } + + /// + /// Whether the end tag of the element is optional. + /// + /// true if the end tag of the element is optional, otherwise false. + public bool EndTagOptional + { + get + { + return m_endTagOptional; + } + } + + /// + /// Whether the start tag of the element is optional. + /// + /// true if the start tag of the element is optional, otherwise false. + public bool StartTagOptional + { + get + { + return m_startTagOptional; + } + } + + /// + /// Finds the attribute definition with the specified name. + /// + /// The name of the to find. + /// The with the specified name. + /// If the attribute list has not yet been initialised. + public AttDef FindAttribute(string name) + { + if (m_attList == null) + throw new InvalidOperationException("The attribute list for the element declaration has not been initialised."); + + AttDef a; + m_attList.TryGetValue(name.ToUpperInvariant(), out a); + return a; + } + + /// + /// Adds attribute definitions to the element declaration. + /// + /// The list of attribute definitions to add. + public void AddAttDefs(Dictionary list) + { + if (list == null) + throw new ArgumentNullException("list"); + + if (m_attList == null) + { + m_attList = list; + } + else + { + foreach (AttDef a in list.Values) + { + if (!m_attList.ContainsKey(a.Name)) + { + m_attList.Add(a.Name, a); + } + } + } + } + + /// + /// Tests whether this element can contain another specified element. + /// + /// The name of the element to check for. + /// The DTD to use to do the check. + /// True if the specified element can be contained by this element. + public bool CanContain(string name, SgmlDtd dtd) + { + // return true if this element is allowed to contain the given element. + if (m_exclusions != null) + { + foreach (string s in m_exclusions) + { + if (string.Equals(s, name, StringComparison.OrdinalIgnoreCase)) + return false; + } + } + + if (m_inclusions != null) + { + foreach (string s in m_inclusions) + { + if (string.Equals(s, name, StringComparison.OrdinalIgnoreCase)) + return true; + } + } + return m_contentModel.CanContain(name, dtd); } - } } /// - /// Tests whether this element can contain another specified element. + /// Where nested subelements cannot occur within an element, its contents can be declared to consist of one of the types of declared content contained in this enumeration. /// - /// The name of the element to check for. - /// The DTD to use to do the check. - /// True if the specified element can be contained by this element. - public bool CanContain(string name, SgmlDtd dtd) + public enum DeclaredContent { - // return true if this element is allowed to contain the given element. - if (m_exclusions != null) - { - foreach (string s in m_exclusions) + /// + /// Not defined. + /// + Default, + + /// + /// Character data (CDATA), which contains only valid SGML characters. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + CDATA, + + /// + /// Replaceable character data (RCDATA), which can contain text, character references and/or general entity references that resolve to character data. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + RCDATA, + + /// + /// Empty element (EMPTY), i.e. having no contents, or contents that can be generated by the program. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + EMPTY + } + + /// + /// Defines the content model for an element. + /// + public class ContentModel + { + private DeclaredContent m_declaredContent; + private int m_currentDepth; + private Group m_model; + + /// + /// Initialises a new instance of the class. + /// + public ContentModel() { - if (string.Equals(s, name, StringComparison.OrdinalIgnoreCase)) + m_model = new Group(null); + } + + /// + /// The number of groups on the stack. + /// + public int CurrentDepth + { + get + { + return m_currentDepth; + } + } + + /// + /// The allowed child content, specifying if nested children are not allowed and if so, what content is allowed. + /// + public DeclaredContent DeclaredContent + { + get + { + return m_declaredContent; + } + } + + /// + /// Begins processing of a nested model group. + /// + public void PushGroup() + { + m_model = new Group(m_model); + m_currentDepth++; + } + + /// + /// Finishes processing of a nested model group. + /// + /// The current depth of the group nesting, or -1 if there are no more groups to pop. + public int PopGroup() + { + if (m_currentDepth == 0) + return -1; + + m_currentDepth--; + m_model.Parent.AddGroup(m_model); + m_model = m_model.Parent; + return m_currentDepth; + } + + /// + /// Adds a new symbol to the current group's members. + /// + /// The symbol to add. + public void AddSymbol(string sym) + { + m_model.AddSymbol(sym); + } + + /// + /// Adds a connector onto the member list for the current group. + /// + /// The connector character to add. + /// + /// If the content is not mixed and has no members yet, or if the group type has been set and the + /// connector does not match the group type. + /// + public void AddConnector(char c) + { + m_model.AddConnector(c); + } + + /// + /// Adds an occurrence character for the current model group, setting it's value. + /// + /// The occurrence character. + public void AddOccurrence(char c) + { + m_model.AddOccurrence(c); + } + + /// + /// Sets the contained content for the content model. + /// + /// The text specified the permissible declared child content. + public void SetDeclaredContent(string dc) + { + // TODO: Validate that this can never combine with nexted groups? + switch (dc) + { + case "EMPTY": + this.m_declaredContent = DeclaredContent.EMPTY; + break; + case "RCDATA": + this.m_declaredContent = DeclaredContent.RCDATA; + break; + case "CDATA": + this.m_declaredContent = DeclaredContent.CDATA; + break; + default: + throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Declared content type '{0}' is not supported", dc)); + } + } + + /// + /// Checks whether an element using this group can contain a specified element. + /// + /// The name of the element to look for. + /// The DTD to use during the checking. + /// true if an element using this group can contain the element, otherwise false. + public bool CanContain(string name, SgmlDtd dtd) + { + if (m_declaredContent != DeclaredContent.Default) + return false; // empty or text only node. + + return m_model.CanContain(name, dtd); + } + } + + /// + /// The type of the content model group, defining the order in which child elements can occur. + /// + public enum GroupType + { + /// + /// No model group. + /// + None, + + /// + /// All elements must occur, in any order. + /// + And, + + /// + /// One (and only one) must occur. + /// + Or, + + /// + /// All element must occur, in the specified order. + /// + Sequence + }; + + /// + /// Qualifies the occurrence of a child element within a content model group. + /// + public enum Occurrence + { + /// + /// The element is required and must occur only once. + /// + Required, + + /// + /// The element is optional and must occur once at most. + /// + Optional, + + /// + /// The element is optional and can be repeated. + /// + ZeroOrMore, + + /// + /// The element must occur at least once or more times. + /// + OneOrMore + } + + /// + /// Defines a group of elements nested within another element. + /// + public class Group + { + private Group m_parent; + private List Members; + private GroupType m_groupType; + private Occurrence m_occurrence; + private bool Mixed; + + /// + /// The of this group. + /// + public Occurrence Occurrence + { + get + { + return m_occurrence; + } + } + + /// + /// Checks whether the group contains only text. + /// + /// true if the group is of mixed content and has no members, otherwise false. + public bool TextOnly + { + get + { + return this.Mixed && Members.Count == 0; + } + } + + /// + /// The parent group of this group. + /// + public Group Parent + { + get + { + return m_parent; + } + } + + /// + /// Initialises a new Content Model Group. + /// + /// The parent model group. + public Group(Group parent) + { + m_parent = parent; + Members = new List(); + m_groupType = GroupType.None; + m_occurrence = Occurrence.Required; + } + + /// + /// Adds a new child model group to the end of the group's members. + /// + /// The model group to add. + public void AddGroup(Group g) + { + Members.Add(g); + } + + /// + /// Adds a new symbol to the group's members. + /// + /// The symbol to add. + public void AddSymbol(string sym) + { + if (string.Equals(sym, "#PCDATA", StringComparison.OrdinalIgnoreCase)) + { + Mixed = true; + } + else + { + Members.Add(sym); + } + } + + /// + /// Adds a connector onto the member list. + /// + /// The connector character to add. + /// + /// If the content is not mixed and has no members yet, or if the group type has been set and the + /// connector does not match the group type. + /// + public void AddConnector(char c) + { + if (!Mixed && Members.Count == 0) + { + throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Missing token before connector '{0}'.", c)); + } + + GroupType gt = GroupType.None; + switch (c) + { + case ',': + gt = GroupType.Sequence; + break; + case '|': + gt = GroupType.Or; + break; + case '&': + gt = GroupType.And; + break; + } + + if (this.m_groupType != GroupType.None && this.m_groupType != gt) + { + throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Connector '{0}' is inconsistent with {1} group.", c, m_groupType.ToString())); + } + + m_groupType = gt; + } + + /// + /// Adds an occurrence character for this group, setting it's value. + /// + /// The occurrence character. + public void AddOccurrence(char c) + { + Occurrence o = Occurrence.Required; + switch (c) + { + case '?': + o = Occurrence.Optional; + break; + case '+': + o = Occurrence.OneOrMore; + break; + case '*': + o = Occurrence.ZeroOrMore; + break; + } + + m_occurrence = o; + } + + /// + /// Checks whether an element using this group can contain a specified element. + /// + /// The name of the element to look for. + /// The DTD to use during the checking. + /// true if an element using this group can contain the element, otherwise false. + /// + /// Rough approximation - this is really assuming an "Or" group + /// + public bool CanContain(string name, SgmlDtd dtd) + { + if (dtd == null) + throw new ArgumentNullException("dtd"); + + // Do a simple search of members. + foreach (object obj in Members) + { + if (obj is string) + { + if( string.Equals((string)obj, name, StringComparison.OrdinalIgnoreCase)) + return true; + } + } + // didn't find it, so do a more expensive search over child elements + // that have optional start tags and over child groups. + foreach (object obj in Members) + { + string s = obj as string; + if (s != null) + { + ElementDecl e = dtd.FindElement(s); + if (e != null) + { + if (e.StartTagOptional) + { + // tricky case, the start tag is optional so element may be + // allowed inside this guy! + if (e.CanContain(name, dtd)) + return true; + } + } + } + else + { + Group m = (Group)obj; + if (m.CanContain(name, dtd)) + return true; + } + } + return false; } - } - - if (m_inclusions != null) - { - foreach (string s in m_inclusions) - { - if (string.Equals(s, name, StringComparison.OrdinalIgnoreCase)) - return true; - } - } - return m_contentModel.CanContain(name, dtd); } - } - - /// - /// Where nested subelements cannot occur within an element, its contents can be declared to consist of one of the types of declared content contained in this enumeration. - /// - public enum DeclaredContent - { - /// - /// Not defined. - /// - Default, /// - /// Character data (CDATA), which contains only valid SGML characters. + /// Defines the different possible attribute types. /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - CDATA, - - /// - /// Replaceable character data (RCDATA), which can contain text, character references and/or general entity references that resolve to character data. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - RCDATA, - - /// - /// Empty element (EMPTY), i.e. having no contents, or contents that can be generated by the program. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - EMPTY - } - - /// - /// Defines the content model for an element. - /// - public class ContentModel - { - private DeclaredContent m_declaredContent; - private int m_currentDepth; - private Group m_model; - - /// - /// Initialises a new instance of the class. - /// - public ContentModel() + public enum AttributeType { - m_model = new Group(null); - } - - /// - /// The number of groups on the stack. - /// - public int CurrentDepth - { - get - { - return m_currentDepth; - } - } - - /// - /// The allowed child content, specifying if nested children are not allowed and if so, what content is allowed. - /// - public DeclaredContent DeclaredContent - { - get - { - return m_declaredContent; - } - } - - /// - /// Begins processing of a nested model group. - /// - public void PushGroup() - { - m_model = new Group(m_model); - m_currentDepth++; - } - - /// - /// Finishes processing of a nested model group. - /// - /// The current depth of the group nesting, or -1 if there are no more groups to pop. - public int PopGroup() - { - if (m_currentDepth == 0) - return -1; - - m_currentDepth--; - m_model.Parent.AddGroup(m_model); - m_model = m_model.Parent; - return m_currentDepth; - } - - /// - /// Adds a new symbol to the current group's members. - /// - /// The symbol to add. - public void AddSymbol(string sym) - { - m_model.AddSymbol(sym); - } - - /// - /// Adds a connector onto the member list for the current group. - /// - /// The connector character to add. - /// - /// If the content is not mixed and has no members yet, or if the group type has been set and the - /// connector does not match the group type. - /// - public void AddConnector(char c) - { - m_model.AddConnector(c); - } - - /// - /// Adds an occurrence character for the current model group, setting it's value. - /// - /// The occurrence character. - public void AddOccurrence(char c) - { - m_model.AddOccurrence(c); - } - - /// - /// Sets the contained content for the content model. - /// - /// The text specified the permissible declared child content. - public void SetDeclaredContent(string dc) - { - // TODO: Validate that this can never combine with nexted groups? - switch (dc) - { - case "EMPTY": - this.m_declaredContent = DeclaredContent.EMPTY; - break; - case "RCDATA": - this.m_declaredContent = DeclaredContent.RCDATA; - break; - case "CDATA": - this.m_declaredContent = DeclaredContent.CDATA; - break; - default: - throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Declared content type '{0}' is not supported", dc)); - } - } - - /// - /// Checks whether an element using this group can contain a specified element. - /// - /// The name of the element to look for. - /// The DTD to use during the checking. - /// true if an element using this group can contain the element, otherwise false. - public bool CanContain(string name, SgmlDtd dtd) - { - if (m_declaredContent != DeclaredContent.Default) - return false; // empty or text only node. - - return m_model.CanContain(name, dtd); - } - } - - /// - /// The type of the content model group, defining the order in which child elements can occur. - /// - public enum GroupType - { - /// - /// No model group. - /// - None, - - /// - /// All elements must occur, in any order. - /// - And, - - /// - /// One (and only one) must occur. - /// - Or, - - /// - /// All element must occur, in the specified order. - /// - Sequence - }; - - /// - /// Qualifies the occurrence of a child element within a content model group. - /// - public enum Occurrence - { - /// - /// The element is required and must occur only once. - /// - Required, - - /// - /// The element is optional and must occur once at most. - /// - Optional, - - /// - /// The element is optional and can be repeated. - /// - ZeroOrMore, - - /// - /// The element must occur at least once or more times. - /// - OneOrMore - } - - /// - /// Defines a group of elements nested within another element. - /// - public class Group - { - private Group m_parent; - private List Members; - private GroupType m_groupType; - private Occurrence m_occurrence; - private bool Mixed; - - /// - /// The of this group. - /// - public Occurrence Occurrence - { - get - { - return m_occurrence; - } - } - - /// - /// Checks whether the group contains only text. - /// - /// true if the group is of mixed content and has no members, otherwise false. - public bool TextOnly - { - get - { - return this.Mixed && Members.Count == 0; - } - } - - /// - /// The parent group of this group. - /// - public Group Parent - { - get - { - return m_parent; - } - } - - /// - /// Initialises a new Content Model Group. - /// - /// The parent model group. - public Group(Group parent) - { - m_parent = parent; - Members = new List(); - m_groupType = GroupType.None; - m_occurrence = Occurrence.Required; - } - - /// - /// Adds a new child model group to the end of the group's members. - /// - /// The model group to add. - public void AddGroup(Group g) - { - Members.Add(g); - } - - /// - /// Adds a new symbol to the group's members. - /// - /// The symbol to add. - public void AddSymbol(string sym) - { - if (string.Equals(sym, "#PCDATA", StringComparison.OrdinalIgnoreCase)) - { - Mixed = true; - } - else - { - Members.Add(sym); - } - } - - /// - /// Adds a connector onto the member list. - /// - /// The connector character to add. - /// - /// If the content is not mixed and has no members yet, or if the group type has been set and the - /// connector does not match the group type. - /// - public void AddConnector(char c) - { - if (!Mixed && Members.Count == 0) - { - throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Missing token before connector '{0}'.", c)); - } - - GroupType gt = GroupType.None; - switch (c) - { - case ',': - gt = GroupType.Sequence; - break; - case '|': - gt = GroupType.Or; - break; - case '&': - gt = GroupType.And; - break; - } - - if (this.m_groupType != GroupType.None && this.m_groupType != gt) - { - throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Connector '{0}' is inconsistent with {1} group.", c, m_groupType.ToString())); - } - - m_groupType = gt; - } - - /// - /// Adds an occurrence character for this group, setting it's value. - /// - /// The occurrence character. - public void AddOccurrence(char c) - { - Occurrence o = Occurrence.Required; - switch (c) - { - case '?': - o = Occurrence.Optional; - break; - case '+': - o = Occurrence.OneOrMore; - break; - case '*': - o = Occurrence.ZeroOrMore; - break; - } - - m_occurrence = o; - } - - /// - /// Checks whether an element using this group can contain a specified element. - /// - /// The name of the element to look for. - /// The DTD to use during the checking. - /// true if an element using this group can contain the element, otherwise false. - /// - /// Rough approximation - this is really assuming an "Or" group - /// - public bool CanContain(string name, SgmlDtd dtd) - { - if (dtd == null) - throw new ArgumentNullException("dtd"); - - // Do a simple search of members. - foreach (object obj in Members) - { - if (obj is string) - { - if (string.Equals((string)obj, name, StringComparison.OrdinalIgnoreCase)) - return true; - } - } - // didn't find it, so do a more expensive search over child elements - // that have optional start tags and over child groups. - foreach (object obj in Members) - { - string s = obj as string; - if (s != null) - { - ElementDecl e = dtd.FindElement(s); - if (e != null) - { - if (e.StartTagOptional) - { - // tricky case, the start tag is optional so element may be - // allowed inside this guy! - if (e.CanContain(name, dtd)) - return true; - } - } - } - else - { - Group m = (Group)obj; - if (m.CanContain(name, dtd)) - return true; - } - } - - return false; - } - } - - /// - /// Defines the different possible attribute types. - /// - public enum AttributeType - { - /// - /// Attribute type not specified. - /// - Default, - - /// - /// The attribute contains text (with no markup). - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - CDATA, - - /// - /// The attribute contains an entity declared in a DTD. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - ENTITY, - - /// - /// The attribute contains a number of entities declared in a DTD. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - ENTITIES, - - /// - /// The attribute is an id attribute uniquely identifie the element it appears on. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - [SuppressMessage("Microsoft.Naming", "CA1706", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - ID, - - /// - /// The attribute value can be any declared subdocument or data entity name. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - IDREF, - - /// - /// The attribute value is a list of (space separated) declared subdocument or data entity names. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - IDREFS, - - /// - /// The attribute value is a SGML Name. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - NAME, - - /// - /// The attribute value is a list of (space separated) SGML Names. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - NAMES, - - /// - /// The attribute value is an XML name token (i.e. contains only name characters, but in this case with digits and other valid name characters accepted as the first character). - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - NMTOKEN, - - /// - /// The attribute value is a list of (space separated) XML NMTokens. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - NMTOKENS, - - /// - /// The attribute value is a number. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - NUMBER, - - /// - /// The attribute value is a list of (space separated) numbers. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - NUMBERS, - - /// - /// The attribute value is a number token (i.e. a name that starts with a number). - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - NUTOKEN, - - /// - /// The attribute value is a list of number tokens. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - NUTOKENS, - - /// - /// Attribute value is a member of the bracketed list of notation names that qualifies this reserved name. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - NOTATION, - - /// - /// The attribute value is one of a set of allowed names. - /// - [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] - ENUMERATION - } - - /// - /// Defines the different constraints on an attribute's presence on an element. - /// - public enum AttributePresence - { - /// - /// The attribute has a default value, and its presence is optional. - /// - Default, - - /// - /// The attribute has a fixed value, if present. - /// - Fixed, - - /// - /// The attribute must always be present on every element. - /// - Required, - - /// - /// The element is optional. - /// - Implied - } - - /// - /// An attribute definition in a DTD. - /// - public class AttDef - { - private string m_name; - private AttributeType m_type; - private string[] m_enumValues; - private string m_default; - private AttributePresence m_presence; - - /// - /// Initialises a new instance of the class. - /// - /// The name of the attribute. - public AttDef(string name) - { - m_name = name; - } - - /// - /// The name of the attribute declared by this attribute definition. - /// - public string Name - { - get - { - return m_name; - } - } - - /// - /// Gets of sets the default value of the attribute. - /// - public string Default - { - get - { - return m_default; - } - set - { - m_default = value; - } - } - - /// - /// The constraints on the attribute's presence on an element. - /// - public AttributePresence AttributePresence - { - get - { - return m_presence; - } - } - - /// - /// Gets or sets the possible enumerated values for the attribute. - /// - [SuppressMessage("Microsoft.Performance", "CA1819", Justification = "Changing this would break backwards compatibility with previous code using this library.")] - public string[] EnumValues - { - get - { - return m_enumValues; - } - } - - /// - /// Sets the attribute definition to have an enumerated value. - /// - /// The possible values in the enumeration. - /// The type to set the attribute to. - /// If the type parameter is not either or . - public void SetEnumeratedType(string[] enumValues, AttributeType type) - { - if (type != AttributeType.ENUMERATION && type != AttributeType.NOTATION) - throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, "AttributeType {0} is not valid for an attribute definition with an enumerated value.", type)); - - m_enumValues = enumValues; - m_type = type; - } - - /// - /// The of the attribute declaration. - /// - public AttributeType Type - { - get - { - return m_type; - } - } - - /// - /// Sets the type of the attribute definition. - /// - /// The string representation of the attribute type, corresponding to the values in the enumeration. - public void SetType(string type) - { - switch (type) - { - case "CDATA": - m_type = AttributeType.CDATA; - break; - case "ENTITY": - m_type = AttributeType.ENTITY; - break; - case "ENTITIES": - m_type = AttributeType.ENTITIES; - break; - case "ID": - m_type = AttributeType.ID; - break; - case "IDREF": - m_type = AttributeType.IDREF; - break; - case "IDREFS": - m_type = AttributeType.IDREFS; - break; - case "NAME": - m_type = AttributeType.NAME; - break; - case "NAMES": - m_type = AttributeType.NAMES; - break; - case "NMTOKEN": - m_type = AttributeType.NMTOKEN; - break; - case "NMTOKENS": - m_type = AttributeType.NMTOKENS; - break; - case "NUMBER": - m_type = AttributeType.NUMBER; - break; - case "NUMBERS": - m_type = AttributeType.NUMBERS; - break; - case "NUTOKEN": - m_type = AttributeType.NUTOKEN; - break; - case "NUTOKENS": - m_type = AttributeType.NUTOKENS; - break; - default: - throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Attribute type '{0}' is not supported", type)); - } - } - - /// - /// Sets the attribute presence declaration. - /// - /// The string representation of the attribute presence, corresponding to one of the values in the enumeration. - /// true if the attribute presence implies the element has a default value. - public bool SetPresence(string token) - { - bool hasDefault = true; - if (string.Equals(token, "FIXED", StringComparison.OrdinalIgnoreCase)) - { - m_presence = AttributePresence.Fixed; - } - else if (string.Equals(token, "REQUIRED", StringComparison.OrdinalIgnoreCase)) - { - m_presence = AttributePresence.Required; - hasDefault = false; - } - else if (string.Equals(token, "IMPLIED", StringComparison.OrdinalIgnoreCase)) - { - m_presence = AttributePresence.Implied; - hasDefault = false; - } - else - { - throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Attribute value '{0}' not supported", token)); - } - - return hasDefault; - } - } - - /* JB: Replaced this with a Dictionary - public class AttList : IEnumerable - { - Hashtable AttDefs; + /// + /// Attribute type not specified. + /// + Default, + + /// + /// The attribute contains text (with no markup). + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + CDATA, - public AttList() - { - AttDefs = new Hashtable(); - } + /// + /// The attribute contains an entity declared in a DTD. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + ENTITY, - public void Add(AttDef a) - { - AttDefs.Add(a.Name, a); - } + /// + /// The attribute contains a number of entities declared in a DTD. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + ENTITIES, + + /// + /// The attribute is an id attribute uniquely identifie the element it appears on. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + [SuppressMessage("Microsoft.Naming", "CA1706", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + ID, + + /// + /// The attribute value can be any declared subdocument or data entity name. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + IDREF, + + /// + /// The attribute value is a list of (space separated) declared subdocument or data entity names. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + IDREFS, + + /// + /// The attribute value is a SGML Name. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + NAME, + + /// + /// The attribute value is a list of (space separated) SGML Names. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + NAMES, + + /// + /// The attribute value is an XML name token (i.e. contains only name characters, but in this case with digits and other valid name characters accepted as the first character). + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + NMTOKEN, - public AttDef this[string name] - { - get - { - return (AttDef)AttDefs[name]; - } - } + /// + /// The attribute value is a list of (space separated) XML NMTokens. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + NMTOKENS, - public IEnumerator GetEnumerator() - { - return AttDefs.Values.GetEnumerator(); - } - } - */ - /// - /// Provides DTD parsing and support for the SgmlParser framework. - /// - public class SgmlDtd - { - private string m_name; - - private Dictionary m_elements; - private Dictionary m_pentities; - private Dictionary m_entities; - private StringBuilder m_sb; - private Entity m_current; - - /// - /// Initialises a new instance of the class. - /// - /// The name of the DTD. - /// The is NOT used. - public SgmlDtd(string name, XmlNameTable nt) - { - this.m_name = name; - this.m_elements = new Dictionary(); - this.m_pentities = new Dictionary(); - this.m_entities = new Dictionary(); - this.m_sb = new StringBuilder(); + /// + /// The attribute value is a number. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + NUMBER, + + /// + /// The attribute value is a list of (space separated) numbers. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + NUMBERS, + + /// + /// The attribute value is a number token (i.e. a name that starts with a number). + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + NUTOKEN, + + /// + /// The attribute value is a list of number tokens. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + NUTOKENS, + + /// + /// Attribute value is a member of the bracketed list of notation names that qualifies this reserved name. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + NOTATION, + + /// + /// The attribute value is one of a set of allowed names. + /// + [SuppressMessage("Microsoft.Naming", "CA1705", Justification = "This capitalisation is appropriate since the value it represents has all upper-case capitalisation.")] + ENUMERATION } /// - /// The name of the DTD. + /// Defines the different constraints on an attribute's presence on an element. /// - public string Name + public enum AttributePresence { - get - { - return m_name; - } + /// + /// The attribute has a default value, and its presence is optional. + /// + Default, + + /// + /// The attribute has a fixed value, if present. + /// + Fixed, + + /// + /// The attribute must always be present on every element. + /// + Required, + + /// + /// The element is optional. + /// + Implied } /// - /// Gets the XmlNameTable associated with this implementation. + /// An attribute definition in a DTD. /// - /// The XmlNameTable enabling you to get the atomized version of a string within the node. - public XmlNameTable NameTable + public class AttDef { - get - { - return null; - } - } + private string m_name; + private AttributeType m_type; + private string[] m_enumValues; + private string m_default; + private AttributePresence m_presence; - /// - /// Parses a DTD and creates a instance that encapsulates the DTD. - /// - /// The base URI of the DTD. - /// The name of the DTD. - /// - /// - /// - /// - /// The is NOT used. - /// A new instance that encapsulates the DTD. - public static SgmlDtd Parse(Uri baseUri, string name, string pubid, string url, string subset, string proxy, XmlNameTable nt) - { - SgmlDtd dtd = new SgmlDtd(name, nt); - if (!string.IsNullOrEmpty(url)) - { - dtd.PushEntity(baseUri, new Entity(dtd.Name, pubid, url, proxy)); - } - - if (!string.IsNullOrEmpty(subset)) - { - dtd.PushEntity(baseUri, new Entity(name, subset)); - } - - try - { - dtd.Parse(); - } - catch (Exception e) - { - throw new SgmlParseException(e.Message + dtd.m_current.Context()); - } - - return dtd; - } - - /// - /// Parses a DTD and creates a instance that encapsulates the DTD. - /// - /// The base URI of the DTD. - /// The name of the DTD. - /// The reader to load the DTD from. - /// - /// The proxy server to use when loading resources. - /// The is NOT used. - /// A new instance that encapsulates the DTD. - [SuppressMessage("Microsoft.Reliability", "CA2000", Justification = "The entities created here are not temporary and should not be disposed here.")] - public static SgmlDtd Parse(Uri baseUri, string name, TextReader input, string subset, string proxy, XmlNameTable nt) - { - SgmlDtd dtd = new SgmlDtd(name, nt); - dtd.PushEntity(baseUri, new Entity(dtd.Name, baseUri, input, proxy)); - if (!string.IsNullOrEmpty(subset)) - { - dtd.PushEntity(baseUri, new Entity(name, subset)); - } - - try - { - dtd.Parse(); - } - catch (Exception e) - { - throw new SgmlParseException(e.Message + dtd.m_current.Context()); - } - - return dtd; - } - - /// - /// Finds an entity in the DTD with the specified name. - /// - /// The name of the to find. - /// The specified Entity from the DTD. - public Entity FindEntity(string name) - { - Entity e; - this.m_entities.TryGetValue(name, out e); - return e; - } - - /// - /// Finds an element declaration in the DTD with the specified name. - /// - /// The name of the to find and return. - /// The matching the specified name. - public ElementDecl FindElement(string name) - { - ElementDecl el; - m_elements.TryGetValue(name.ToUpperInvariant(), out el); - return el; - } - - //-------------------------------- Parser ------------------------- - private void PushEntity(Uri baseUri, Entity e) - { - e.Open(this.m_current, baseUri); - this.m_current = e; - this.m_current.ReadChar(); - } - - private void PopEntity() - { - if (this.m_current != null) this.m_current.Close(); - if (this.m_current.Parent != null) - { - this.m_current = this.m_current.Parent; - } - else - { - this.m_current = null; - } - } - - private void Parse() - { - char ch = this.m_current.Lastchar; - while (true) - { - switch (ch) + /// + /// Initialises a new instance of the class. + /// + /// The name of the attribute. + public AttDef(string name) { - case Entity.EOF: - PopEntity(); - if (this.m_current == null) - return; - ch = this.m_current.Lastchar; - break; - case ' ': - case '\n': - case '\r': - case '\t': - ch = this.m_current.ReadChar(); - break; - case '<': - ParseMarkup(); - ch = this.m_current.ReadChar(); - break; - case '%': - Entity e = ParseParameterEntity(SgmlDtd.WhiteSpace); + m_name = name; + } + + /// + /// The name of the attribute declared by this attribute definition. + /// + public string Name + { + get + { + return m_name; + } + } + + /// + /// Gets of sets the default value of the attribute. + /// + public string Default + { + get + { + return m_default; + } + set + { + m_default = value; + } + } + + /// + /// The constraints on the attribute's presence on an element. + /// + public AttributePresence AttributePresence + { + get + { + return m_presence; + } + } + + /// + /// Gets or sets the possible enumerated values for the attribute. + /// + [SuppressMessage("Microsoft.Performance", "CA1819", Justification = "Changing this would break backwards compatibility with previous code using this library.")] + public string[] EnumValues + { + get + { + return m_enumValues; + } + } + + /// + /// Sets the attribute definition to have an enumerated value. + /// + /// The possible values in the enumeration. + /// The type to set the attribute to. + /// If the type parameter is not either or . + public void SetEnumeratedType(string[] enumValues, AttributeType type) + { + if (type != AttributeType.ENUMERATION && type != AttributeType.NOTATION) + throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, "AttributeType {0} is not valid for an attribute definition with an enumerated value.", type)); + + m_enumValues = enumValues; + m_type = type; + } + + /// + /// The of the attribute declaration. + /// + public AttributeType Type + { + get + { + return m_type; + } + } + + /// + /// Sets the type of the attribute definition. + /// + /// The string representation of the attribute type, corresponding to the values in the enumeration. + public void SetType(string type) + { + switch (type) + { + case "CDATA": + m_type = AttributeType.CDATA; + break; + case "ENTITY": + m_type = AttributeType.ENTITY; + break; + case "ENTITIES": + m_type = AttributeType.ENTITIES; + break; + case "ID": + m_type = AttributeType.ID; + break; + case "IDREF": + m_type = AttributeType.IDREF; + break; + case "IDREFS": + m_type = AttributeType.IDREFS; + break; + case "NAME": + m_type = AttributeType.NAME; + break; + case "NAMES": + m_type = AttributeType.NAMES; + break; + case "NMTOKEN": + m_type = AttributeType.NMTOKEN; + break; + case "NMTOKENS": + m_type = AttributeType.NMTOKENS; + break; + case "NUMBER": + m_type = AttributeType.NUMBER; + break; + case "NUMBERS": + m_type = AttributeType.NUMBERS; + break; + case "NUTOKEN": + m_type = AttributeType.NUTOKEN; + break; + case "NUTOKENS": + m_type = AttributeType.NUTOKENS; + break; + default: + throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Attribute type '{0}' is not supported", type)); + } + } + + /// + /// Sets the attribute presence declaration. + /// + /// The string representation of the attribute presence, corresponding to one of the values in the enumeration. + /// true if the attribute presence implies the element has a default value. + public bool SetPresence(string token) + { + bool hasDefault = true; + if (string.Equals(token, "FIXED", StringComparison.OrdinalIgnoreCase)) + { + m_presence = AttributePresence.Fixed; + } + else if (string.Equals(token, "REQUIRED", StringComparison.OrdinalIgnoreCase)) + { + m_presence = AttributePresence.Required; + hasDefault = false; + } + else if (string.Equals(token, "IMPLIED", StringComparison.OrdinalIgnoreCase)) + { + m_presence = AttributePresence.Implied; + hasDefault = false; + } + else + { + throw new SgmlParseException(string.Format(CultureInfo.CurrentUICulture, "Attribute value '{0}' not supported", token)); + } + + return hasDefault; + } + } + +/* JB: Replaced this with a Dictionary + public class AttList : IEnumerable + { + Hashtable AttDefs; + + public AttList() + { + AttDefs = new Hashtable(); + } + + public void Add(AttDef a) + { + AttDefs.Add(a.Name, a); + } + + public AttDef this[string name] + { + get + { + return (AttDef)AttDefs[name]; + } + } + + public IEnumerator GetEnumerator() + { + return AttDefs.Values.GetEnumerator(); + } + } +*/ + /// + /// Provides DTD parsing and support for the SgmlParser framework. + /// + public class SgmlDtd + { + private string m_name; + + private Dictionary m_elements; + private Dictionary m_pentities; + private Dictionary m_entities; + private StringBuilder m_sb; + private Entity m_current; + + /// + /// Initialises a new instance of the class. + /// + /// The name of the DTD. + /// The is NOT used. + public SgmlDtd(string name, XmlNameTable nt) + { + this.m_name = name; + this.m_elements = new Dictionary(); + this.m_pentities = new Dictionary(); + this.m_entities = new Dictionary(); + this.m_sb = new StringBuilder(); + } + + /// + /// The name of the DTD. + /// + public string Name + { + get + { + return m_name; + } + } + + /// + /// Gets the XmlNameTable associated with this implementation. + /// + /// The XmlNameTable enabling you to get the atomized version of a string within the node. + public XmlNameTable NameTable + { + get + { + return null; + } + } + + /// + /// Parses a DTD and creates a instance that encapsulates the DTD. + /// + /// The base URI of the DTD. + /// The name of the DTD. + /// + /// + /// + /// + /// The is NOT used. + /// A new instance that encapsulates the DTD. + public static SgmlDtd Parse(Uri baseUri, string name, string pubid, string url, string subset, string proxy, XmlNameTable nt) + { + SgmlDtd dtd = new SgmlDtd(name, nt); + if (!string.IsNullOrEmpty(url)) + { + dtd.PushEntity(baseUri, new Entity(dtd.Name, pubid, url, proxy)); + } + + if (!string.IsNullOrEmpty(subset)) + { + dtd.PushEntity(baseUri, new Entity(name, subset)); + } + + try + { + dtd.Parse(); + } + catch (Exception e) + { + throw new SgmlParseException(e.Message + dtd.m_current.Context()); + } + + return dtd; + } + + /// + /// Parses a DTD and creates a instance that encapsulates the DTD. + /// + /// The base URI of the DTD. + /// The name of the DTD. + /// The reader to load the DTD from. + /// + /// The proxy server to use when loading resources. + /// The is NOT used. + /// A new instance that encapsulates the DTD. + [SuppressMessage("Microsoft.Reliability", "CA2000", Justification = "The entities created here are not temporary and should not be disposed here.")] + public static SgmlDtd Parse(Uri baseUri, string name, TextReader input, string subset, string proxy, XmlNameTable nt) + { + SgmlDtd dtd = new SgmlDtd(name, nt); + dtd.PushEntity(baseUri, new Entity(dtd.Name, baseUri, input, proxy)); + if (!string.IsNullOrEmpty(subset)) + { + dtd.PushEntity(baseUri, new Entity(name, subset)); + } + try { - PushEntity(this.m_current.ResolvedUri, e); - } - catch (Exception ex) + dtd.Parse(); + } + catch (Exception e) { - // BUG: need an error log. - Debug.WriteLine(ex.Message + this.m_current.Context()); + throw new SgmlParseException(e.Message + dtd.m_current.Context()); } - ch = this.m_current.Lastchar; - break; - default: - this.m_current.Error("Unexpected character '{0}'", ch); - break; + + return dtd; } - } - } - void ParseMarkup() - { - char ch = this.m_current.ReadChar(); - if (ch != '!') - { - this.m_current.Error("Found '{0}', but expecing declaration starting with '"); - } - else if (ch == '[') - { - ParseMarkedSection(); - } - else - { - string token = this.m_current.ScanToken(this.m_sb, SgmlDtd.WhiteSpace, true); - switch (token) + /// + /// Finds an entity in the DTD with the specified name. + /// + /// The name of the to find. + /// The specified Entity from the DTD. + public Entity FindEntity(string name) { - case "ENTITY": - ParseEntity(); - break; - case "ELEMENT": - ParseElementDecl(); - break; - case "ATTLIST": - ParseAttList(); - break; - default: - this.m_current.Error("Invalid declaration ' - this.m_current.ReadChar(); // move to next char. - string name = ScanName("["); - if (string.Equals(name, "INCLUDE", StringComparison.OrdinalIgnoreCase)) - { - ParseIncludeSection(); - } - else if (string.Equals(name, "IGNORE", StringComparison.OrdinalIgnoreCase)) - { - ParseIgnoreSection(); - } - else - { - this.m_current.Error("Unsupported marked section type '{0}'", name); - } - } - - [SuppressMessage("Microsoft.Performance", "CA1822", Justification = "This is not yet implemented and will use 'this' in the future.")] - [SuppressMessage("Microsoft.Globalization", "CA1303", Justification = "The use of a literal here is only due to this not yet being implemented.")] - private void ParseIncludeSection() - { - throw new NotImplementedException("Include Section"); - } - - void ParseIgnoreSection() - { - char ch = this.m_current.SkipWhitespace(); - if (ch != '[') this.m_current.Error("Expecting '[' but found {0}", ch); - this.m_current.ScanToEnd(this.m_sb, "Conditional Section", "]]>"); - } - - string ScanName(string term) - { - // skip whitespace, scan name (which may be parameter entity reference - // which is then expanded to a name) - char ch = this.m_current.SkipWhitespace(); - if (ch == '%') - { - Entity e = ParseParameterEntity(term); - ch = this.m_current.Lastchar; - // bugbug - need to support external and nested parameter entities - if (!e.IsInternal) throw new NotSupportedException("External parameter entity resolution"); - return e.Literal.Trim(); - } - else - { - return this.m_current.ScanToken(this.m_sb, term, true); - } - } - - private Entity ParseParameterEntity(string term) - { - // almost the same as this.current.ScanToken, except we also terminate on ';' - this.m_current.ReadChar(); - string name = this.m_current.ScanToken(this.m_sb, ";" + term, false); - if (this.m_current.Lastchar == ';') - this.m_current.ReadChar(); - Entity e = GetParameterEntity(name); - return e; - } - - private Entity GetParameterEntity(string name) - { - Entity e = null; - m_pentities.TryGetValue(name, out e); - if (e == null) - this.m_current.Error("Reference to undefined parameter entity '{0}'", name); - - return e; - } - - /// - /// Returns a dictionary for looking up entities by their value. - /// - /// A dictionary for looking up entities by their value. - [SuppressMessage("Microsoft.Design", "CA1024", Justification = "This method creates and copies a dictionary, so exposing it as a property is not appropriate.")] - public Dictionary GetEntitiesLiteralNameLookup() - { - Dictionary hashtable = new Dictionary(); - foreach (Entity entity in this.m_entities.Values) - hashtable[entity.Literal] = entity; - - return hashtable; - } - - private const string WhiteSpace = " \r\n\t"; - - private void ParseEntity() - { - char ch = this.m_current.SkipWhitespace(); - bool pe = (ch == '%'); - if (pe) - { - // parameter entity. - this.m_current.ReadChar(); // move to next char - ch = this.m_current.SkipWhitespace(); - } - string name = this.m_current.ScanToken(this.m_sb, SgmlDtd.WhiteSpace, true); - ch = this.m_current.SkipWhitespace(); - Entity e = null; - if (ch == '"' || ch == '\'') - { - string literal = this.m_current.ScanLiteral(this.m_sb, ch); - e = new Entity(name, literal); - } - else - { - string pubid = null; - string extid = null; - string tok = this.m_current.ScanToken(this.m_sb, SgmlDtd.WhiteSpace, true); - if (Entity.IsLiteralType(tok)) + /// + /// Finds an element declaration in the DTD with the specified name. + /// + /// The name of the to find and return. + /// The matching the specified name. + public ElementDecl FindElement(string name) { - ch = this.m_current.SkipWhitespace(); - string literal = this.m_current.ScanLiteral(this.m_sb, ch); - e = new Entity(name, literal); - e.SetLiteralType(tok); + ElementDecl el; + m_elements.TryGetValue(name.ToUpperInvariant(), out el); + return el; } - else + + //-------------------------------- Parser ------------------------- + private void PushEntity(Uri baseUri, Entity e) { - extid = tok; - if (string.Equals(extid, "PUBLIC", StringComparison.OrdinalIgnoreCase)) - { + e.Open(this.m_current, baseUri); + this.m_current = e; + this.m_current.ReadChar(); + } + + private void PopEntity() + { + if (this.m_current != null) this.m_current.Close(); + if (this.m_current.Parent != null) + { + this.m_current = this.m_current.Parent; + } + else + { + this.m_current = null; + } + } + + private void Parse() + { + char ch = this.m_current.Lastchar; + while (true) + { + switch (ch) + { + case Entity.EOF: + PopEntity(); + if (this.m_current == null) + return; + ch = this.m_current.Lastchar; + break; + case ' ': + case '\n': + case '\r': + case '\t': + ch = this.m_current.ReadChar(); + break; + case '<': + ParseMarkup(); + ch = this.m_current.ReadChar(); + break; + case '%': + Entity e = ParseParameterEntity(SgmlDtd.WhiteSpace); + try + { + PushEntity(this.m_current.ResolvedUri, e); + } + catch (Exception ex) + { + // BUG: need an error log. + Debug.WriteLine(ex.Message + this.m_current.Context()); + } + ch = this.m_current.Lastchar; + break; + default: + this.m_current.Error("Unexpected character '{0}'", ch); + break; + } + } + } + + void ParseMarkup() + { + char ch = this.m_current.ReadChar(); + if (ch != '!') + { + this.m_current.Error("Found '{0}', but expecing declaration starting with '"); + } + else if (ch == '[') + { + ParseMarkedSection(); + } + else + { + string token = this.m_current.ScanToken(this.m_sb, SgmlDtd.WhiteSpace, true); + switch (token) + { + case "ENTITY": + ParseEntity(); + break; + case "ELEMENT": + ParseElementDecl(); + break; + case "ATTLIST": + ParseAttList(); + break; + default: + this.m_current.Error("Invalid declaration ' + this.m_current.ReadChar(); // move to next char. + string name = ScanName("["); + if (string.Equals(name, "INCLUDE", StringComparison.OrdinalIgnoreCase)) + { + ParseIncludeSection(); + } + else if (string.Equals(name, "IGNORE", StringComparison.OrdinalIgnoreCase)) + { + ParseIgnoreSection(); + } + else + { + this.m_current.Error("Unsupported marked section type '{0}'", name); + } + } + + [SuppressMessage("Microsoft.Performance", "CA1822", Justification = "This is not yet implemented and will use 'this' in the future.")] + [SuppressMessage("Microsoft.Globalization", "CA1303", Justification = "The use of a literal here is only due to this not yet being implemented.")] + private void ParseIncludeSection() + { + throw new NotImplementedException("Include Section"); + } + + void ParseIgnoreSection() + { + char ch = this.m_current.SkipWhitespace(); + if (ch != '[') this.m_current.Error("Expecting '[' but found {0}", ch); + this.m_current.ScanToEnd(this.m_sb, "Conditional Section", "]]>"); + } + + string ScanName(string term) + { + // skip whitespace, scan name (which may be parameter entity reference + // which is then expanded to a name) + char ch = this.m_current.SkipWhitespace(); + if (ch == '%') + { + Entity e = ParseParameterEntity(term); + ch = this.m_current.Lastchar; + // bugbug - need to support external and nested parameter entities + if (!e.IsInternal) throw new NotSupportedException("External parameter entity resolution"); + return e.Literal.Trim(); + } + else + { + return this.m_current.ScanToken(this.m_sb, term, true); + } + } + + private Entity ParseParameterEntity(string term) + { + // almost the same as this.current.ScanToken, except we also terminate on ';' + this.m_current.ReadChar(); + string name = this.m_current.ScanToken(this.m_sb, ";"+term, false); + if (this.m_current.Lastchar == ';') + this.m_current.ReadChar(); + Entity e = GetParameterEntity(name); + return e; + } + + private Entity GetParameterEntity(string name) + { + Entity e = null; + m_pentities.TryGetValue(name, out e); + if (e == null) + this.m_current.Error("Reference to undefined parameter entity '{0}'", name); + + return e; + } + + /// + /// Returns a dictionary for looking up entities by their value. + /// + /// A dictionary for looking up entities by their value. + [SuppressMessage("Microsoft.Design", "CA1024", Justification = "This method creates and copies a dictionary, so exposing it as a property is not appropriate.")] + public Dictionary GetEntitiesLiteralNameLookup() + { + Dictionary hashtable = new Dictionary(); + foreach (Entity entity in this.m_entities.Values) + hashtable[entity.Literal] = entity; + + return hashtable; + } + + private const string WhiteSpace = " \r\n\t"; + + private void ParseEntity() + { + char ch = this.m_current.SkipWhitespace(); + bool pe = (ch == '%'); + if (pe) + { + // parameter entity. + this.m_current.ReadChar(); // move to next char + ch = this.m_current.SkipWhitespace(); + } + string name = this.m_current.ScanToken(this.m_sb, SgmlDtd.WhiteSpace, true); ch = this.m_current.SkipWhitespace(); - if (ch == '"' || ch == '\'') + Entity e = null; + if (ch == '"' || ch == '\'') { - pubid = this.m_current.ScanLiteral(this.m_sb, ch); + string literal = this.m_current.ScanLiteral(this.m_sb, ch); + e = new Entity(name, literal); + } + else + { + string pubid = null; + string extid = null; + string tok = this.m_current.ScanToken(this.m_sb, SgmlDtd.WhiteSpace, true); + if (Entity.IsLiteralType(tok)) + { + ch = this.m_current.SkipWhitespace(); + string literal = this.m_current.ScanLiteral(this.m_sb, ch); + e = new Entity(name, literal); + e.SetLiteralType(tok); + } + else + { + extid = tok; + if (string.Equals(extid, "PUBLIC", StringComparison.OrdinalIgnoreCase)) + { + ch = this.m_current.SkipWhitespace(); + if (ch == '"' || ch == '\'') + { + pubid = this.m_current.ScanLiteral(this.m_sb, ch); + } + else + { + this.m_current.Error("Expecting public identifier literal but found '{0}'",ch); + } + } + else if (!string.Equals(extid, "SYSTEM", StringComparison.OrdinalIgnoreCase)) + { + this.m_current.Error("Invalid external identifier '{0}'. Expecing 'PUBLIC' or 'SYSTEM'.", extid); + } + string uri = null; + ch = this.m_current.SkipWhitespace(); + if (ch == '"' || ch == '\'') + { + uri = this.m_current.ScanLiteral(this.m_sb, ch); + } + else if (ch != '>') + { + this.m_current.Error("Expecting system identifier literal but found '{0}'",ch); + } + e = new Entity(name, pubid, uri, this.m_current.Proxy); + } + } + ch = this.m_current.SkipWhitespace(); + if (ch == '-') + ch = ParseDeclComments(); + if (ch != '>') + { + this.m_current.Error("Expecting end of entity declaration '>' but found '{0}'", ch); + } + if (pe) + this.m_pentities.Add(e.Name, e); + else + this.m_entities.Add(e.Name, e); + } + + private void ParseElementDecl() + { + char ch = this.m_current.SkipWhitespace(); + string[] names = ParseNameGroup(ch, true); + ch = char.ToUpperInvariant(this.m_current.SkipWhitespace()); + bool sto = false; + bool eto = false; + if (ch == 'O' || ch == '-') { + sto = (ch == 'O'); // start tag optional? + this.m_current.ReadChar(); + ch = char.ToUpperInvariant(this.m_current.SkipWhitespace()); + if (ch == 'O' || ch == '-'){ + eto = (ch == 'O'); // end tag optional? + ch = this.m_current.ReadChar(); + } + } + ch = this.m_current.SkipWhitespace(); + ContentModel cm = ParseContentModel(ch); + ch = this.m_current.SkipWhitespace(); + + string [] exclusions = null; + string [] inclusions = null; + + if (ch == '-') + { + ch = this.m_current.ReadChar(); + if (ch == '(') + { + exclusions = ParseNameGroup(ch, true); + ch = this.m_current.SkipWhitespace(); + } + else if (ch == '-') + { + ch = ParseDeclComment(false); + } + else + { + this.m_current.Error("Invalid syntax at '{0}'", ch); + } + } + + if (ch == '-') + ch = ParseDeclComments(); + + if (ch == '+') + { + ch = this.m_current.ReadChar(); + if (ch != '(') + { + this.m_current.Error("Expecting inclusions name group", ch); + } + inclusions = ParseNameGroup(ch, true); + ch = this.m_current.SkipWhitespace(); + } + + if (ch == '-') + ch = ParseDeclComments(); + + + if (ch != '>') + { + this.m_current.Error("Expecting end of ELEMENT declaration '>' but found '{0}'", ch); + } + + foreach (string name in names) + { + string atom = name.ToUpperInvariant(); + this.m_elements.Add(atom, new ElementDecl(atom, sto, eto, cm, inclusions, exclusions)); + } + } + + static string ngterm = " \r\n\t|,)"; + string[] ParseNameGroup(char ch, bool nmtokens) + { + var names = new List(); + if (ch == '(') + { + ch = this.m_current.ReadChar(); + ch = this.m_current.SkipWhitespace(); + while (ch != ')') + { + // skip whitespace, scan name (which may be parameter entity reference + // which is then expanded to a name) + ch = this.m_current.SkipWhitespace(); + if (ch == '%') + { + Entity e = ParseParameterEntity(SgmlDtd.ngterm); + PushEntity(this.m_current.ResolvedUri, e); + ParseNameList(names, nmtokens); + PopEntity(); + ch = this.m_current.Lastchar; + } + else + { + string token = this.m_current.ScanToken(this.m_sb, SgmlDtd.ngterm, nmtokens); + token = token.ToUpperInvariant(); + names.Add(token); + } + ch = this.m_current.SkipWhitespace(); + if (ch == '|' || ch == ',') ch = this.m_current.ReadChar(); + } + this.m_current.ReadChar(); // consume ')' + } + else + { + string name = this.m_current.ScanToken(this.m_sb, SgmlDtd.WhiteSpace, nmtokens); + name = name.ToUpperInvariant(); + names.Add(name); + } + return names.ToArray(); + } + + void ParseNameList(IList names, bool nmtokens) + { + char ch = this.m_current.Lastchar; + ch = this.m_current.SkipWhitespace(); + while (ch != Entity.EOF) + { + string name; + if (ch == '%') + { + Entity e = ParseParameterEntity(SgmlDtd.ngterm); + PushEntity(this.m_current.ResolvedUri, e); + ParseNameList(names, nmtokens); + PopEntity(); + ch = this.m_current.Lastchar; + } + else + { + name = this.m_current.ScanToken(this.m_sb, SgmlDtd.ngterm, true); + name = name.ToUpperInvariant(); + names.Add(name); + } + ch = this.m_current.SkipWhitespace(); + if (ch == '|') + { + ch = this.m_current.ReadChar(); + ch = this.m_current.SkipWhitespace(); + } + } + } + + static string dcterm = " \r\n\t>"; + private ContentModel ParseContentModel(char ch) + { + ContentModel cm = new ContentModel(); + if (ch == '(') + { + this.m_current.ReadChar(); + ParseModel(')', cm); + ch = this.m_current.ReadChar(); + if (ch == '?' || ch == '+' || ch == '*') + { + cm.AddOccurrence(ch); + this.m_current.ReadChar(); + } + } + else if (ch == '%') + { + Entity e = ParseParameterEntity(SgmlDtd.dcterm); + PushEntity(this.m_current.ResolvedUri, e); + cm = ParseContentModel(this.m_current.Lastchar); + PopEntity(); // bugbug should be at EOF. } else { - this.m_current.Error("Expecting public identifier literal but found '{0}'", ch); + string dc = ScanName(SgmlDtd.dcterm); + cm.SetDeclaredContent(dc); } - } - else if (!string.Equals(extid, "SYSTEM", StringComparison.OrdinalIgnoreCase)) - { - this.m_current.Error("Invalid external identifier '{0}'. Expecing 'PUBLIC' or 'SYSTEM'.", extid); - } - string uri = null; - ch = this.m_current.SkipWhitespace(); - if (ch == '"' || ch == '\'') - { - uri = this.m_current.ScanLiteral(this.m_sb, ch); - } - else if (ch != '>') - { - this.m_current.Error("Expecting system identifier literal but found '{0}'", ch); - } - e = new Entity(name, pubid, uri, this.m_current.Proxy); + return cm; } - } - ch = this.m_current.SkipWhitespace(); - if (ch == '-') - ch = ParseDeclComments(); - if (ch != '>') - { - this.m_current.Error("Expecting end of entity declaration '>' but found '{0}'", ch); - } - if (pe) - this.m_pentities.Add(e.Name, e); - else - this.m_entities.Add(e.Name, e); - } - private void ParseElementDecl() - { - char ch = this.m_current.SkipWhitespace(); - string[] names = ParseNameGroup(ch, true); - ch = char.ToUpperInvariant(this.m_current.SkipWhitespace()); - bool sto = false; - bool eto = false; - if (ch == 'O' || ch == '-') - { - sto = (ch == 'O'); // start tag optional? - this.m_current.ReadChar(); - ch = char.ToUpperInvariant(this.m_current.SkipWhitespace()); - if (ch == 'O' || ch == '-') + static string cmterm = " \r\n\t,&|()?+*"; + void ParseModel(char cmt, ContentModel cm) { - eto = (ch == 'O'); // end tag optional? - ch = this.m_current.ReadChar(); - } - } - ch = this.m_current.SkipWhitespace(); - ContentModel cm = ParseContentModel(ch); - ch = this.m_current.SkipWhitespace(); - - string[] exclusions = null; - string[] inclusions = null; - - if (ch == '-') - { - ch = this.m_current.ReadChar(); - if (ch == '(') - { - exclusions = ParseNameGroup(ch, true); - ch = this.m_current.SkipWhitespace(); - } - else if (ch == '-') - { - ch = ParseDeclComment(false); - } - else - { - this.m_current.Error("Invalid syntax at '{0}'", ch); - } - } - - if (ch == '-') - ch = ParseDeclComments(); - - if (ch == '+') - { - ch = this.m_current.ReadChar(); - if (ch != '(') - { - this.m_current.Error("Expecting inclusions name group", ch); - } - inclusions = ParseNameGroup(ch, true); - ch = this.m_current.SkipWhitespace(); - } - - if (ch == '-') - ch = ParseDeclComments(); - - - if (ch != '>') - { - this.m_current.Error("Expecting end of ELEMENT declaration '>' but found '{0}'", ch); - } - - foreach (string name in names) - { - string atom = name.ToUpperInvariant(); - this.m_elements.Add(atom, new ElementDecl(atom, sto, eto, cm, inclusions, exclusions)); - } - } - - static string ngterm = " \r\n\t|,)"; - string[] ParseNameGroup(char ch, bool nmtokens) - { - var names = new List(); - if (ch == '(') - { - ch = this.m_current.ReadChar(); - ch = this.m_current.SkipWhitespace(); - while (ch != ')') - { - // skip whitespace, scan name (which may be parameter entity reference - // which is then expanded to a name) - ch = this.m_current.SkipWhitespace(); - if (ch == '%') - { - Entity e = ParseParameterEntity(SgmlDtd.ngterm); - PushEntity(this.m_current.ResolvedUri, e); - ParseNameList(names, nmtokens); - PopEntity(); - ch = this.m_current.Lastchar; - } - else - { - string token = this.m_current.ScanToken(this.m_sb, SgmlDtd.ngterm, nmtokens); - token = token.ToUpperInvariant(); - names.Add(token); - } - ch = this.m_current.SkipWhitespace(); - if (ch == '|' || ch == ',') ch = this.m_current.ReadChar(); - } - this.m_current.ReadChar(); // consume ')' - } - else - { - string name = this.m_current.ScanToken(this.m_sb, SgmlDtd.WhiteSpace, nmtokens); - name = name.ToUpperInvariant(); - names.Add(name); - } - return names.ToArray(); - } - - void ParseNameList(IList names, bool nmtokens) - { - char ch = this.m_current.Lastchar; - ch = this.m_current.SkipWhitespace(); - while (ch != Entity.EOF) - { - string name; - if (ch == '%') - { - Entity e = ParseParameterEntity(SgmlDtd.ngterm); - PushEntity(this.m_current.ResolvedUri, e); - ParseNameList(names, nmtokens); - PopEntity(); - ch = this.m_current.Lastchar; - } - else - { - name = this.m_current.ScanToken(this.m_sb, SgmlDtd.ngterm, true); - name = name.ToUpperInvariant(); - names.Add(name); - } - ch = this.m_current.SkipWhitespace(); - if (ch == '|') - { - ch = this.m_current.ReadChar(); - ch = this.m_current.SkipWhitespace(); - } - } - } - - static string dcterm = " \r\n\t>"; - private ContentModel ParseContentModel(char ch) - { - ContentModel cm = new ContentModel(); - if (ch == '(') - { - this.m_current.ReadChar(); - ParseModel(')', cm); - ch = this.m_current.ReadChar(); - if (ch == '?' || ch == '+' || ch == '*') - { - cm.AddOccurrence(ch); - this.m_current.ReadChar(); - } - } - else if (ch == '%') - { - Entity e = ParseParameterEntity(SgmlDtd.dcterm); - PushEntity(this.m_current.ResolvedUri, e); - cm = ParseContentModel(this.m_current.Lastchar); - PopEntity(); // bugbug should be at EOF. - } - else - { - string dc = ScanName(SgmlDtd.dcterm); - cm.SetDeclaredContent(dc); - } - return cm; - } - - static string cmterm = " \r\n\t,&|()?+*"; - void ParseModel(char cmt, ContentModel cm) - { - // Called when part of the model is made up of the contents of a parameter entity - int depth = cm.CurrentDepth; - char ch = this.m_current.Lastchar; - ch = this.m_current.SkipWhitespace(); - while (ch != cmt || cm.CurrentDepth > depth) // the entity must terminate while inside the content model. - { - if (ch == Entity.EOF) - { - this.m_current.Error("Content Model was not closed"); - } - if (ch == '%') - { - Entity e = ParseParameterEntity(SgmlDtd.cmterm); - PushEntity(this.m_current.ResolvedUri, e); - ParseModel(Entity.EOF, cm); - PopEntity(); - ch = this.m_current.SkipWhitespace(); - } - else if (ch == '(') - { - cm.PushGroup(); - this.m_current.ReadChar();// consume '(' - ch = this.m_current.SkipWhitespace(); - } - else if (ch == ')') - { - ch = this.m_current.ReadChar();// consume ')' - if (ch == '*' || ch == '+' || ch == '?') - { - cm.AddOccurrence(ch); - ch = this.m_current.ReadChar(); - } - if (cm.PopGroup() < depth) - { - this.m_current.Error("Parameter entity cannot close a paren outside it's own scope"); - } - ch = this.m_current.SkipWhitespace(); - } - else if (ch == ',' || ch == '|' || ch == '&') - { - cm.AddConnector(ch); - this.m_current.ReadChar(); // skip connector - ch = this.m_current.SkipWhitespace(); - } - else - { - string token; - if (ch == '#') - { - ch = this.m_current.ReadChar(); - token = "#" + this.m_current.ScanToken(this.m_sb, SgmlDtd.cmterm, true); // since '#' is not a valid name character. - } - else - { - token = this.m_current.ScanToken(this.m_sb, SgmlDtd.cmterm, true); - } - - token = token.ToUpperInvariant(); - ch = this.m_current.Lastchar; - if (ch == '?' || ch == '+' || ch == '*') - { - cm.PushGroup(); - cm.AddSymbol(token); - cm.AddOccurrence(ch); - cm.PopGroup(); - this.m_current.ReadChar(); // skip connector + // Called when part of the model is made up of the contents of a parameter entity + int depth = cm.CurrentDepth; + char ch = this.m_current.Lastchar; ch = this.m_current.SkipWhitespace(); - } - else - { - cm.AddSymbol(token); + while (ch != cmt || cm.CurrentDepth > depth) // the entity must terminate while inside the content model. + { + if (ch == Entity.EOF) + { + this.m_current.Error("Content Model was not closed"); + } + if (ch == '%') + { + Entity e = ParseParameterEntity(SgmlDtd.cmterm); + PushEntity(this.m_current.ResolvedUri, e); + ParseModel(Entity.EOF, cm); + PopEntity(); + ch = this.m_current.SkipWhitespace(); + } + else if (ch == '(') + { + cm.PushGroup(); + this.m_current.ReadChar();// consume '(' + ch = this.m_current.SkipWhitespace(); + } + else if (ch == ')') + { + ch = this.m_current.ReadChar();// consume ')' + if (ch == '*' || ch == '+' || ch == '?') + { + cm.AddOccurrence(ch); + ch = this.m_current.ReadChar(); + } + if (cm.PopGroup() < depth) + { + this.m_current.Error("Parameter entity cannot close a paren outside it's own scope"); + } + ch = this.m_current.SkipWhitespace(); + } + else if (ch == ',' || ch == '|' || ch == '&') + { + cm.AddConnector(ch); + this.m_current.ReadChar(); // skip connector + ch = this.m_current.SkipWhitespace(); + } + else + { + string token; + if (ch == '#') + { + ch = this.m_current.ReadChar(); + token = "#" + this.m_current.ScanToken(this.m_sb, SgmlDtd.cmterm, true); // since '#' is not a valid name character. + } + else + { + token = this.m_current.ScanToken(this.m_sb, SgmlDtd.cmterm, true); + } + + token = token.ToUpperInvariant(); + ch = this.m_current.Lastchar; + if (ch == '?' || ch == '+' || ch == '*') + { + cm.PushGroup(); + cm.AddSymbol(token); + cm.AddOccurrence(ch); + cm.PopGroup(); + this.m_current.ReadChar(); // skip connector + ch = this.m_current.SkipWhitespace(); + } + else + { + cm.AddSymbol(token); + ch = this.m_current.SkipWhitespace(); + } + } + } + } + + void ParseAttList() + { + char ch = this.m_current.SkipWhitespace(); + string[] names = ParseNameGroup(ch, true); + Dictionary attlist = new Dictionary(); + ParseAttList(attlist, '>'); + foreach (string name in names) + { + ElementDecl e; + if (!m_elements.TryGetValue(name, out e)) + { + this.m_current.Error("ATTLIST references undefined ELEMENT {0}", name); + } + + e.AddAttDefs(attlist); + } + } + + static string peterm = " \t\r\n>"; + void ParseAttList(Dictionary list, char term) + { + char ch = this.m_current.SkipWhitespace(); + while (ch != term) + { + if (ch == '%') + { + Entity e = ParseParameterEntity(SgmlDtd.peterm); + PushEntity(this.m_current.ResolvedUri, e); + ParseAttList(list, Entity.EOF); + PopEntity(); + ch = this.m_current.SkipWhitespace(); + } + else if (ch == '-') + { + ch = ParseDeclComments(); + } + else + { + AttDef a = ParseAttDef(ch); + list.Add(a.Name, a); + } + ch = this.m_current.SkipWhitespace(); + } + } + + AttDef ParseAttDef(char ch) + { ch = this.m_current.SkipWhitespace(); - } + string name = ScanName(SgmlDtd.WhiteSpace); + name = name.ToUpperInvariant(); + AttDef attdef = new AttDef(name); + + ch = this.m_current.SkipWhitespace(); + if (ch == '-') + ch = ParseDeclComments(); + + ParseAttType(ch, attdef); + + ch = this.m_current.SkipWhitespace(); + if (ch == '-') + ch = ParseDeclComments(); + + ParseAttDefault(ch, attdef); + + ch = this.m_current.SkipWhitespace(); + if (ch == '-') + ch = ParseDeclComments(); + + return attdef; + + } + + void ParseAttType(char ch, AttDef attdef) + { + if (ch == '%') + { + Entity e = ParseParameterEntity(SgmlDtd.WhiteSpace); + PushEntity(this.m_current.ResolvedUri, e); + ParseAttType(this.m_current.Lastchar, attdef); + PopEntity(); // bugbug - are we at the end of the entity? + ch = this.m_current.Lastchar; + return; + } + + if (ch == '(') + { + //attdef.EnumValues = ParseNameGroup(ch, false); + //attdef.Type = AttributeType.ENUMERATION; + attdef.SetEnumeratedType(ParseNameGroup(ch, false), AttributeType.ENUMERATION); + } + else + { + string token = ScanName(SgmlDtd.WhiteSpace); + if (string.Equals(token, "NOTATION", StringComparison.OrdinalIgnoreCase)) + { + ch = this.m_current.SkipWhitespace(); + if (ch != '(') + { + this.m_current.Error("Expecting name group '(', but found '{0}'", ch); + } + //attdef.Type = AttributeType.NOTATION; + //attdef.EnumValues = ParseNameGroup(ch, true); + attdef.SetEnumeratedType(ParseNameGroup(ch, true), AttributeType.NOTATION); + } + else + { + attdef.SetType(token); + } + } + } + + void ParseAttDefault(char ch, AttDef attdef) + { + if (ch == '%') + { + Entity e = ParseParameterEntity(SgmlDtd.WhiteSpace); + PushEntity(this.m_current.ResolvedUri, e); + ParseAttDefault(this.m_current.Lastchar, attdef); + PopEntity(); // bugbug - are we at the end of the entity? + ch = this.m_current.Lastchar; + return; + } + + bool hasdef = true; + if (ch == '#') + { + this.m_current.ReadChar(); + string token = this.m_current.ScanToken(this.m_sb, SgmlDtd.WhiteSpace, true); + hasdef = attdef.SetPresence(token); + ch = this.m_current.SkipWhitespace(); + } + if (hasdef) + { + if (ch == '\'' || ch == '"') + { + string lit = this.m_current.ScanLiteral(this.m_sb, ch); + attdef.Default = lit; + ch = this.m_current.SkipWhitespace(); + } + else + { + string name = this.m_current.ScanToken(this.m_sb, SgmlDtd.WhiteSpace, false); + name = name.ToUpperInvariant(); + attdef.Default = name; // bugbug - must be one of the enumerated names. + ch = this.m_current.SkipWhitespace(); + } + } } - } } - void ParseAttList() + internal static class StringUtilities { - char ch = this.m_current.SkipWhitespace(); - string[] names = ParseNameGroup(ch, true); - Dictionary attlist = new Dictionary(); - ParseAttList(attlist, '>'); - foreach (string name in names) - { - ElementDecl e; - if (!m_elements.TryGetValue(name, out e)) - { - this.m_current.Error("ATTLIST references undefined ELEMENT {0}", name); + public static bool EqualsIgnoreCase(string a, string b){ + return string.Equals(a, b, StringComparison.OrdinalIgnoreCase); } - - e.AddAttDefs(attlist); - } } - static string peterm = " \t\r\n>"; - void ParseAttList(Dictionary list, char term) + internal static class UTF32Utilities { - char ch = this.m_current.SkipWhitespace(); - while (ch != term) - { - if (ch == '%') + public static string ConvertFromUtf32(int utf32) { - Entity e = ParseParameterEntity(SgmlDtd.peterm); - PushEntity(this.m_current.ResolvedUri, e); - ParseAttList(list, Entity.EOF); - PopEntity(); - ch = this.m_current.SkipWhitespace(); - } - else if (ch == '-') - { - ch = ParseDeclComments(); - } - else - { - AttDef a = ParseAttDef(ch); - list.Add(a.Name, a); - } - ch = this.m_current.SkipWhitespace(); - } - } - - AttDef ParseAttDef(char ch) - { - ch = this.m_current.SkipWhitespace(); - string name = ScanName(SgmlDtd.WhiteSpace); - name = name.ToUpperInvariant(); - AttDef attdef = new AttDef(name); - - ch = this.m_current.SkipWhitespace(); - if (ch == '-') - ch = ParseDeclComments(); - - ParseAttType(ch, attdef); - - ch = this.m_current.SkipWhitespace(); - if (ch == '-') - ch = ParseDeclComments(); - - ParseAttDefault(ch, attdef); - - ch = this.m_current.SkipWhitespace(); - if (ch == '-') - ch = ParseDeclComments(); - - return attdef; - - } - - void ParseAttType(char ch, AttDef attdef) - { - if (ch == '%') - { - Entity e = ParseParameterEntity(SgmlDtd.WhiteSpace); - PushEntity(this.m_current.ResolvedUri, e); - ParseAttType(this.m_current.Lastchar, attdef); - PopEntity(); // bugbug - are we at the end of the entity? - ch = this.m_current.Lastchar; - return; - } - - if (ch == '(') - { - //attdef.EnumValues = ParseNameGroup(ch, false); - //attdef.Type = AttributeType.ENUMERATION; - attdef.SetEnumeratedType(ParseNameGroup(ch, false), AttributeType.ENUMERATION); - } - else - { - string token = ScanName(SgmlDtd.WhiteSpace); - if (string.Equals(token, "NOTATION", StringComparison.OrdinalIgnoreCase)) - { - ch = this.m_current.SkipWhitespace(); - if (ch != '(') - { - this.m_current.Error("Expecting name group '(', but found '{0}'", ch); - } - //attdef.Type = AttributeType.NOTATION; - //attdef.EnumValues = ParseNameGroup(ch, true); - attdef.SetEnumeratedType(ParseNameGroup(ch, true), AttributeType.NOTATION); - } - else - { - attdef.SetType(token); - } - } - } - - void ParseAttDefault(char ch, AttDef attdef) - { - if (ch == '%') - { - Entity e = ParseParameterEntity(SgmlDtd.WhiteSpace); - PushEntity(this.m_current.ResolvedUri, e); - ParseAttDefault(this.m_current.Lastchar, attdef); - PopEntity(); // bugbug - are we at the end of the entity? - ch = this.m_current.Lastchar; - return; - } - - bool hasdef = true; - if (ch == '#') - { - this.m_current.ReadChar(); - string token = this.m_current.ScanToken(this.m_sb, SgmlDtd.WhiteSpace, true); - hasdef = attdef.SetPresence(token); - ch = this.m_current.SkipWhitespace(); - } - if (hasdef) - { - if (ch == '\'' || ch == '"') - { - string lit = this.m_current.ScanLiteral(this.m_sb, ch); - attdef.Default = lit; - ch = this.m_current.SkipWhitespace(); - } - else - { - string name = this.m_current.ScanToken(this.m_sb, SgmlDtd.WhiteSpace, false); - name = name.ToUpperInvariant(); - attdef.Default = name; // bugbug - must be one of the enumerated names. - ch = this.m_current.SkipWhitespace(); - } - } - } - } - - internal static class StringUtilities - { - public static bool EqualsIgnoreCase(string a, string b) - { - return string.Equals(a, b, StringComparison.OrdinalIgnoreCase); - } - } - - internal static class UTF32Utilities - { - public static string ConvertFromUtf32(int utf32) - { - if (utf32 < 0 || utf32 > 0x10FFFF) - throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF."); - if (0xD800 <= utf32 && utf32 <= 0xDFFF) - throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range."); - if (utf32 < 0x10000) - return new string((char)utf32, 1); - utf32 -= 0x10000; - return new string( - new char[] {(char) ((utf32 >> 10) + 0xD800), +#if PORTABLE + if (utf32 < 0 || utf32 > 0x10FFFF) + throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF."); + if (0xD800 <= utf32 && utf32 <= 0xDFFF) + throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range."); + if (utf32 < 0x10000) + return new string((char)utf32, 1); + utf32 -= 0x10000; + return new string( + new char[] {(char) ((utf32 >> 10) + 0xD800), (char) (utf32 % 0x0400 + 0xDC00)}); - } +#else + return char.ConvertFromUtf32(utf32); +#endif + } - public static int ConvertToUtf32(char highSurrogate, char lowSurrogate) - { - if (highSurrogate < 0xD800 || 0xDBFF < highSurrogate) - throw new ArgumentOutOfRangeException("highSurrogate"); - if (lowSurrogate < 0xDC00 || 0xDFFF < lowSurrogate) - throw new ArgumentOutOfRangeException("lowSurrogate"); + public static int ConvertToUtf32(char highSurrogate, char lowSurrogate) + { +#if PORTABLE + if (highSurrogate < 0xD800 || 0xDBFF < highSurrogate) + throw new ArgumentOutOfRangeException("highSurrogate"); + if (lowSurrogate < 0xDC00 || 0xDFFF < lowSurrogate) + throw new ArgumentOutOfRangeException("lowSurrogate"); - return 0x10000 + ((highSurrogate - 0xD800) << 10) + (lowSurrogate - 0xDC00); + return 0x10000 + ((highSurrogate - 0xD800) << 10) + (lowSurrogate - 0xDC00); +#else + return char.ConvertToUtf32(highSurrogate, lowSurrogate); +#endif + } } - } } diff --git a/PortablePorts/SgmlReader/SgmlReader.cs b/PortablePorts/SgmlReader/SgmlReader.cs index 52dead9..1039e64 100644 --- a/PortablePorts/SgmlReader/SgmlReader.cs +++ b/PortablePorts/SgmlReader/SgmlReader.cs @@ -1,4 +1,4 @@ -/* +/* * * An XmlReader implementation for loading SGML (including HTML) converting it * to well formed XML, by adding missing quotes, empty attribute values, ignoring @@ -42,2703 +42,2720 @@ using System.Xml; namespace Sgml { - /// - /// SGML is case insensitive, so here you can choose between converting - /// to lower case or upper case tags. "None" means that the case is left - /// alone, except that end tags will be folded to match the start tags. - /// - public enum CaseFolding - { /// - /// Do not convert case, except for converting end tags to match start tags. + /// SGML is case insensitive, so here you can choose between converting + /// to lower case or upper case tags. "None" means that the case is left + /// alone, except that end tags will be folded to match the start tags. /// - None, - - /// - /// Convert tags to upper case. - /// - ToUpper, - - /// - /// Convert tags to lower case. - /// - ToLower - } - - /// - /// System.Xml.WhitespaceHandling is not available in .NETPortable. - /// - public enum WhitespaceHandling - { - All, - None, - Significant - } - - /// - /// This stack maintains a high water mark for allocated objects so the client - /// can reuse the objects in the stack to reduce memory allocations, this is - /// used to maintain current state of the parser for element stack, and attributes - /// in each element. - /// - internal class HWStack - { - private object[] m_items; - private int m_size; - private int m_count; - private int m_growth; - - /// - /// Initialises a new instance of the HWStack class. - /// - /// The amount to grow the stack space by, if more space is needed on the stack. - public HWStack(int growth) + public enum CaseFolding { - this.m_growth = growth; + /// + /// Do not convert case, except for converting end tags to match start tags. + /// + None, + + /// + /// Convert tags to upper case. + /// + ToUpper, + + /// + /// Convert tags to lower case. + /// + ToLower } +#if PORTABLE /// - /// The number of items currently in the stack. + /// System.Xml.WhitespaceHandling is not available in .NETPortable. /// - public int Count + public enum WhitespaceHandling { - get - { - return this.m_count; - } - set - { - this.m_count = value; - } + All, + None, + Significant } +#endif /// - /// The size (capacity) of the stack. + /// This stack maintains a high water mark for allocated objects so the client + /// can reuse the objects in the stack to reduce memory allocations, this is + /// used to maintain current state of the parser for element stack, and attributes + /// in each element. /// - [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] - public int Size + internal class HWStack { - get - { - return this.m_size; - } - } + private object[] m_items; + private int m_size; + private int m_count; + private int m_growth; - /// - /// Returns the item at the requested index or null if index is out of bounds - /// - /// The index of the item to retrieve. - /// The item at the requested index or null if index is out of bounds. - public object this[int i] - { - get - { - return (i >= 0 && i < this.m_size) ? m_items[i] : null; - } - set - { - this.m_items[i] = value; - } - } - - /// - /// Removes and returns the item at the top of the stack - /// - /// The item at the top of the stack. - public object Pop() - { - this.m_count--; - if (this.m_count > 0) - { - return m_items[this.m_count - 1]; - } - - return null; - } - - /// - /// Pushes a new slot at the top of the stack. - /// - /// The object at the top of the stack. - /// - /// This method tries to reuse a slot, if it returns null then - /// the user has to call the other Push method. - /// - public object Push() - { - if (this.m_count == this.m_size) - { - int newsize = this.m_size + this.m_growth; - object[] newarray = new object[newsize]; - if (this.m_items != null) - Array.Copy(this.m_items, newarray, this.m_size); - - this.m_size = newsize; - this.m_items = newarray; - } - return m_items[this.m_count++]; - } - - /// - /// Remove a specific item from the stack. - /// - /// The index of the item to remove. - [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] - public void RemoveAt(int i) - { - this.m_items[i] = null; - Array.Copy(this.m_items, i + 1, this.m_items, i, this.m_count - i - 1); - this.m_count--; - } - } - - /// - /// This class represents an attribute. The AttDef is assigned - /// from a validation process, and is used to provide default values. - /// - internal class Attribute - { - internal string Name; // the atomized name. - internal AttDef DtdType; // the AttDef of the attribute from the SGML DTD. - internal char QuoteChar; // the quote character used for the attribute value. - private string m_literalValue; // the attribute value - - /// - /// Attribute objects are reused during parsing to reduce memory allocations, - /// hence the Reset method. - /// - public void Reset(string name, string value, char quote) - { - this.Name = name; - this.m_literalValue = value; - this.QuoteChar = quote; - this.DtdType = null; - } - - public string Value - { - get - { - if (this.m_literalValue != null) - return this.m_literalValue; - if (this.DtdType != null) - return this.DtdType.Default; - return null; - } - /* set - { - this.m_literalValue = value; - }*/ - } - - public bool IsDefault - { - get - { - return (this.m_literalValue == null); - } - } - } - - /// - /// This class models an XML node, an array of elements in scope is maintained while parsing - /// for validation purposes, and these Node objects are reused to reduce object allocation, - /// hence the reset method. - /// - internal class Node - { - internal XmlNodeType NodeType; - internal string Value; - internal XmlSpace Space; - internal string XmlLang; - internal bool IsEmpty; - internal string Name; - internal ElementDecl DtdType; // the DTD type found via validation - internal State CurrentState; - internal bool Simulated; // tag was injected into result stream. - HWStack attributes = new HWStack(10); - - /// - /// Attribute objects are reused during parsing to reduce memory allocations, - /// hence the Reset method. - /// - public void Reset(string name, XmlNodeType nt, string value) - { - this.Value = value; - this.Name = name; - this.NodeType = nt; - this.Space = XmlSpace.None; - this.XmlLang = null; - this.IsEmpty = true; - this.attributes.Count = 0; - this.DtdType = null; - } - - public Attribute AddAttribute(string name, string value, char quotechar, bool caseInsensitive) - { - Attribute a; - // check for duplicates! - for (int i = 0, n = this.attributes.Count; i < n; i++) - { - a = (Attribute)this.attributes[i]; - if (string.Equals(a.Name, name, caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) + /// + /// Initialises a new instance of the HWStack class. + /// + /// The amount to grow the stack space by, if more space is needed on the stack. + public HWStack(int growth) { - return null; - } - } - // This code makes use of the high water mark for attribute objects, - // and reuses exisint Attribute objects to avoid memory allocation. - a = (Attribute)this.attributes.Push(); - if (a == null) - { - a = new Attribute(); - this.attributes[this.attributes.Count - 1] = a; - } - a.Reset(name, value, quotechar); - return a; - } - - [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] - public void RemoveAttribute(string name) - { - for (int i = 0, n = this.attributes.Count; i < n; i++) - { - Attribute a = (Attribute)this.attributes[i]; - if (string.Equals(a.Name, name, StringComparison.OrdinalIgnoreCase)) - { - this.attributes.RemoveAt(i); - return; - } - } - } - public void CopyAttributes(Node n) - { - for (int i = 0, len = n.attributes.Count; i < len; i++) - { - Attribute a = (Attribute)n.attributes[i]; - Attribute na = this.AddAttribute(a.Name, a.Value, a.QuoteChar, false); - na.DtdType = a.DtdType; - } - } - - public int AttributeCount - { - get - { - return this.attributes.Count; - } - } - - public int GetAttribute(string name) - { - for (int i = 0, n = this.attributes.Count; i < n; i++) - { - Attribute a = (Attribute)this.attributes[i]; - if (string.Equals(a.Name, name, StringComparison.OrdinalIgnoreCase)) - { - return i; - } - } - return -1; - } - - public Attribute GetAttribute(int i) - { - if (i >= 0 && i < this.attributes.Count) - { - Attribute a = (Attribute)this.attributes[i]; - return a; - } - return null; - } - } - - internal enum State - { - Initial, // The initial state (Read has not been called yet) - Markup, // Expecting text or markup - EndTag, // Positioned on an end tag - Attr, // Positioned on an attribute - AttrValue, // Positioned in an attribute value - Text, // Positioned on a Text node. - PartialTag, // Positioned on a text node, and we have hit a start tag - AutoClose, // We are auto-closing tags (this is like State.EndTag), but end tag was generated - CData, // We are on a CDATA type node, eg. where we have special parsing rules. - PartialText, - PseudoStartTag, // we pushed a pseudo-start tag, need to continue with previous start tag. - Eof - } - - - /// - /// SgmlReader is an XmlReader API over any SGML document (including built in - /// support for HTML). - /// - public class SgmlReader : XmlReader - { - /// - /// The value returned when a namespace is queried and none has been specified. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1705", Justification = "SgmlReader's standards for constants are different to Microsoft's and in line with older C++ style constants naming conventions. Visually, constants using this style are more easily identifiable as such.")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707", Justification = "SgmlReader's standards for constants are different to Microsoft's and in line with older C++ style constants naming conventions. Visually, constants using this style are more easily identifiable as such.")] - public const string UNDEFINED_NAMESPACE = "#unknown"; - - private SgmlDtd m_dtd; - private Entity m_current; - private State m_state; - private char m_partial; - private string m_endTag; - private HWStack m_stack; - private Node m_node; // current node (except for attributes) - // Attributes are handled separately using these members. - private Attribute m_a; - private int m_apos; // which attribute are we positioned on in the collection. - private Uri m_baseUri; - private StringBuilder m_sb; - private StringBuilder m_name; - private TextWriter m_log; - private bool m_foundRoot; - private bool m_ignoreDtd; - - // autoclose support - private Node m_newnode; - private int m_poptodepth; - private int m_rootCount; - private bool m_isHtml; - private string m_rootElementName; - - private string m_href; - private string m_errorLogFile; - private Entity m_lastError; - private string m_proxy; - private TextReader m_inputStream; - private string m_syslit; - private string m_pubid; - private string m_subset; - private string m_docType; - private WhitespaceHandling m_whitespaceHandling; - private CaseFolding m_folding = CaseFolding.None; - private bool m_stripDocType = true; - //private string m_startTag; - private Dictionary unknownNamespaces = new Dictionary(); - - /// - /// Initialises a new instance of the SgmlReader class. - /// - public SgmlReader() - { - Init(); - } - - /// - /// Initialises a new instance of the SgmlReader class with an existing , which is NOT used. - /// - /// The nametable to use. - public SgmlReader(XmlNameTable nt) - { - Init(); - } - - /// - /// Specify the SgmlDtd object directly. This allows you to cache the Dtd and share - /// it across multipl SgmlReaders. To load a DTD from a URL use the SystemLiteral property. - /// - public SgmlDtd Dtd - { - get - { - if (this.m_dtd == null) - { - LazyLoadDtd(this.m_baseUri); + this.m_growth = growth; } - return this.m_dtd; - } - set - { - this.m_dtd = value; - } - } - - private void LazyLoadDtd(Uri baseUri) - { - if (this.m_dtd == null && !this.m_ignoreDtd) - { - if (string.IsNullOrEmpty(this.m_syslit)) + /// + /// The number of items currently in the stack. + /// + public int Count { - if (this.m_docType != null && StringUtilities.EqualsIgnoreCase(this.m_docType, "html")) - { - Assembly a = Assembly.GetExecutingAssembly(); - string name = a.FullName.Split(',')[0] + ".Html.dtd"; - Stream stm = a.GetManifestResourceStream(name); - if (stm != null) + get { - StreamReader sr = new StreamReader(stm); - this.m_dtd = SgmlDtd.Parse(baseUri, "HTML", sr, null, this.m_proxy, null); + return this.m_count; } - } - } - else - { - if (baseUri != null) - { - baseUri = new Uri(baseUri, this.m_syslit); - } - else if (this.m_baseUri != null) - { - baseUri = new Uri(this.m_baseUri, this.m_syslit); - } - else - { - throw new InvalidOperationException("Cannot load DTD without specifying base URI."); - } - this.m_dtd = SgmlDtd.Parse(baseUri, this.m_docType, this.m_pubid, baseUri.AbsoluteUri, this.m_subset, this.m_proxy, null); - } - } - - if (this.m_dtd != null && this.m_dtd.Name != null) - { - switch (this.CaseFolding) - { - case CaseFolding.ToUpper: - this.m_rootElementName = this.m_dtd.Name.ToUpperInvariant(); - break; - case CaseFolding.ToLower: - this.m_rootElementName = this.m_dtd.Name.ToLowerInvariant(); - break; - default: - this.m_rootElementName = this.m_dtd.Name; - break; - } - - this.m_isHtml = StringUtilities.EqualsIgnoreCase(this.m_dtd.Name, "html"); - } - } - - /// - /// The name of root element specified in the DOCTYPE tag. - /// - public string DocType - { - get - { - return this.m_docType; - } - set - { - this.m_docType = value; - } - } - - /// - /// The root element of the document. - /// - public string RootElementName - { - get - { - return m_rootElementName; - } - } - - /// - /// The PUBLIC identifier in the DOCTYPE tag - /// - public string PublicIdentifier - { - get - { - return this.m_pubid; - } - set - { - this.m_pubid = value; - } - } - - /// - /// The SYSTEM literal in the DOCTYPE tag identifying the location of the DTD. - /// - public string SystemLiteral - { - get - { - return this.m_syslit; - } - set - { - this.m_syslit = value; - } - } - - /// - /// The DTD internal subset in the DOCTYPE tag - /// - public string InternalSubset - { - get - { - return this.m_subset; - } - set - { - this.m_subset = value; - } - } - - /// - /// The input stream containing SGML data to parse. - /// You must specify this property or the Href property before calling Read(). - /// - public TextReader InputStream - { - get - { - return this.m_inputStream; - } - set - { - this.m_inputStream = value; - Init(); - } - } - - /// - /// Sometimes you need to specify a proxy server in order to load data via HTTP - /// from outside the firewall. For example: "itgproxy:80". - /// - public string WebProxy - { - get - { - return this.m_proxy; - } - set - { - this.m_proxy = value; - } - } - - /// - /// The base Uri is used to resolve relative Uri's like the SystemLiteral and - /// Href properties. This is a method because BaseURI is a read-only - /// property on the base XmlReader class. - /// - public void SetBaseUri(string uri) - { - this.m_baseUri = new Uri(uri); - } - - /// - /// Specify the location of the input SGML document as a URL. - /// - public string Href - { - get - { - return this.m_href; - } - set - { - this.m_href = value; - Init(); - if (this.m_baseUri == null) - { - if (this.m_href.IndexOf("://") > 0) - { - this.m_baseUri = new Uri(this.m_href); - } - else - { - throw new ArgumentException("Relative URLs are not supported."); - } - } - } - } - - /// - /// Whether to strip out the DOCTYPE tag from the output (default true) - /// - public bool StripDocType - { - get - { - return this.m_stripDocType; - } - set - { - this.m_stripDocType = value; - } - } - - /// - /// Gets or sets a value indicating whether to ignore any DTD reference. - /// - /// true if DTD references should be ignored; otherwise, false. - public bool IgnoreDtd - { - get { return m_ignoreDtd; } - set { m_ignoreDtd = value; } - } - - /// - /// The case conversion behaviour while processing tags. - /// - public CaseFolding CaseFolding - { - get - { - return this.m_folding; - } - set - { - this.m_folding = value; - } - } - - /// - /// DTD validation errors are written to this stream. - /// - public TextWriter ErrorLog - { - get - { - return this.m_log; - } - set - { - this.m_log = value; - } - } - - /// - /// DTD validation errors are written to this log file. - /// - [Obsolete("Not supported on the .NETPortable runtime.")] - public string ErrorLogFile - { - get - { - throw new NotSupportedException(); - } - set - { - throw new NotSupportedException(); - } - } - - private void Log(string msg, params string[] args) - { - if (ErrorLog != null) - { - string err = string.Format(CultureInfo.CurrentUICulture, msg, args); - if (this.m_lastError != this.m_current) - { - err = err + " " + this.m_current.Context(); - this.m_lastError = this.m_current; - ErrorLog.WriteLine("### Error:" + err); - } - else - { - string path = ""; - if (this.m_current.ResolvedUri != null) - { - path = this.m_current.ResolvedUri.AbsolutePath; - } - - ErrorLog.WriteLine("### Error in {0}#{1}, line {2}, position {3}: {4}", path, this.m_current.Name, this.m_current.Line, this.m_current.LinePosition, err); - } - } - } - - private void Log(string msg, char ch) - { - Log(msg, ch.ToString()); - } - - private void Init() - { - this.m_state = State.Initial; - this.m_stack = new HWStack(10); - this.m_node = Push(null, XmlNodeType.Document, null); - this.m_node.IsEmpty = false; - this.m_sb = new StringBuilder(); - this.m_name = new StringBuilder(); - this.m_poptodepth = 0; - this.m_current = null; - this.m_partial = '\0'; - this.m_endTag = null; - this.m_a = null; - this.m_apos = 0; - this.m_newnode = null; - this.m_rootCount = 0; - this.m_foundRoot = false; - this.unknownNamespaces.Clear(); - } - - private Node Push(string name, XmlNodeType nt, string value) - { - Node result = (Node)this.m_stack.Push(); - if (result == null) - { - result = new Node(); - this.m_stack[this.m_stack.Count - 1] = result; - } - - result.Reset(name, nt, value); - this.m_node = result; - return result; - } - - private void SwapTopNodes() - { - int top = this.m_stack.Count - 1; - if (top > 0) - { - Node n = (Node)this.m_stack[top - 1]; - this.m_stack[top - 1] = this.m_stack[top]; - this.m_stack[top] = n; - } - } - - private Node Push(Node n) - { - // we have to do a deep clone of the Node object because - // it is reused in the stack. - Node n2 = Push(n.Name, n.NodeType, n.Value); - n2.DtdType = n.DtdType; - n2.IsEmpty = n.IsEmpty; - n2.Space = n.Space; - n2.XmlLang = n.XmlLang; - n2.CurrentState = n.CurrentState; - n2.CopyAttributes(n); - this.m_node = n2; - return n2; - } - - private void Pop() - { - if (this.m_stack.Count > 1) - { - this.m_node = (Node)this.m_stack.Pop(); - } - } - - private Node Top() - { - int top = this.m_stack.Count - 1; - if (top > 0) - { - return (Node)this.m_stack[top]; - } - - return null; - } - - /// - /// The node type of the node currently being parsed. - /// - public override XmlNodeType NodeType - { - get - { - if (this.m_state == State.Attr) - { - return XmlNodeType.Attribute; - } - else if (this.m_state == State.AttrValue) - { - return XmlNodeType.Text; - } - else if (this.m_state == State.EndTag || this.m_state == State.AutoClose) - { - return XmlNodeType.EndElement; - } - - return this.m_node.NodeType; - } - } - - /// - /// The name of the current node, if currently positioned on a node or attribute. - /// - public override string Name - { - get - { - string result = null; - if (this.m_state == State.Attr) - { - result = XmlConvert.EncodeName(this.m_a.Name); - } - else if (this.m_state != State.AttrValue) - { - result = this.m_node.Name; - } - - return result; - } - } - - /// - /// The local name of the current node, if currently positioned on a node or attribute. - /// - public override string LocalName - { - get - { - string result = Name; - if (result != null) - { - int colon = result.IndexOf(':'); - if (colon != -1) - { - result = result.Substring(colon + 1); - } - } - return result; - } - } - - /// - /// The namespace of the current node, if currently positioned on a node or attribute. - /// - /// - /// If not positioned on a node or attribute, is returned. - /// - [SuppressMessage("Microsoft.Performance", "CA1820", Justification = "Cannot use IsNullOrEmpty in a switch statement and swapping the elegance of switch for a load of 'if's is not worth it.")] - public override string NamespaceURI - { - get - { - // SGML has no namespaces, unless this turned out to be an xmlns attribute. - if (this.m_state == State.Attr && string.Equals(this.m_a.Name, "xmlns", StringComparison.OrdinalIgnoreCase)) - { - return "http://www.w3.org/2000/xmlns/"; - } - - string prefix = Prefix; - switch (Prefix) - { - case "xmlns": - return "http://www.w3.org/2000/xmlns/"; - case "xml": - return "http://www.w3.org/XML/1998/namespace"; - case null: // Should never occur since Prefix never returns null - case "": - if (NodeType == XmlNodeType.Attribute) + set { - // attributes without a prefix are never in any namespace - return string.Empty; + this.m_count = value; } - else if (NodeType == XmlNodeType.Element) + } + + /// + /// The size (capacity) of the stack. + /// + [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] + public int Size + { + get { - // check if a 'xmlns:prefix' attribute is defined - for (int i = this.m_stack.Count - 1; i > 0; --i) - { - Node node = this.m_stack[i] as Node; - if ((node != null) && (node.NodeType == XmlNodeType.Element)) + return this.m_size; + } + } + + /// + /// Returns the item at the requested index or null if index is out of bounds + /// + /// The index of the item to retrieve. + /// The item at the requested index or null if index is out of bounds. + public object this[int i] + { + get + { + return (i >= 0 && i < this.m_size) ? m_items[i] : null; + } + set + { + this.m_items[i] = value; + } + } + + /// + /// Removes and returns the item at the top of the stack + /// + /// The item at the top of the stack. + public object Pop() + { + this.m_count--; + if (this.m_count > 0) + { + return m_items[this.m_count - 1]; + } + + return null; + } + + /// + /// Pushes a new slot at the top of the stack. + /// + /// The object at the top of the stack. + /// + /// This method tries to reuse a slot, if it returns null then + /// the user has to call the other Push method. + /// + public object Push() + { + if (this.m_count == this.m_size) + { + int newsize = this.m_size + this.m_growth; + object[] newarray = new object[newsize]; + if (this.m_items != null) + Array.Copy(this.m_items, newarray, this.m_size); + + this.m_size = newsize; + this.m_items = newarray; + } + return m_items[this.m_count++]; + } + + /// + /// Remove a specific item from the stack. + /// + /// The index of the item to remove. + [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] + public void RemoveAt(int i) + { + this.m_items[i] = null; + Array.Copy(this.m_items, i + 1, this.m_items, i, this.m_count - i - 1); + this.m_count--; + } + } + + /// + /// This class represents an attribute. The AttDef is assigned + /// from a validation process, and is used to provide default values. + /// + internal class Attribute + { + internal string Name; // the atomized name. + internal AttDef DtdType; // the AttDef of the attribute from the SGML DTD. + internal char QuoteChar; // the quote character used for the attribute value. + private string m_literalValue; // the attribute value + + /// + /// Attribute objects are reused during parsing to reduce memory allocations, + /// hence the Reset method. + /// + public void Reset(string name, string value, char quote) + { + this.Name = name; + this.m_literalValue = value; + this.QuoteChar = quote; + this.DtdType = null; + } + + public string Value + { + get + { + if (this.m_literalValue != null) + return this.m_literalValue; + if (this.DtdType != null) + return this.DtdType.Default; + return null; + } +/* set + { + this.m_literalValue = value; + }*/ + } + + public bool IsDefault + { + get + { + return (this.m_literalValue == null); + } + } + } + + /// + /// This class models an XML node, an array of elements in scope is maintained while parsing + /// for validation purposes, and these Node objects are reused to reduce object allocation, + /// hence the reset method. + /// + internal class Node + { + internal XmlNodeType NodeType; + internal string Value; + internal XmlSpace Space; + internal string XmlLang; + internal bool IsEmpty; + internal string Name; + internal ElementDecl DtdType; // the DTD type found via validation + internal State CurrentState; + internal bool Simulated; // tag was injected into result stream. + HWStack attributes = new HWStack(10); + + /// + /// Attribute objects are reused during parsing to reduce memory allocations, + /// hence the Reset method. + /// + public void Reset(string name, XmlNodeType nt, string value) { + this.Value = value; + this.Name = name; + this.NodeType = nt; + this.Space = XmlSpace.None; + this.XmlLang= null; + this.IsEmpty = true; + this.attributes.Count = 0; + this.DtdType = null; + } + + public Attribute AddAttribute(string name, string value, char quotechar, bool caseInsensitive) { + Attribute a; + // check for duplicates! + for (int i = 0, n = this.attributes.Count; i < n; i++) { + a = (Attribute)this.attributes[i]; + if (string.Equals(a.Name, name, caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) { - int index = node.GetAttribute("xmlns"); - if (index >= 0) - { - string value = node.GetAttribute(index).Value; - if (value != null) - { - return value; - } - } + return null; + } + } + // This code makes use of the high water mark for attribute objects, + // and reuses exisint Attribute objects to avoid memory allocation. + a = (Attribute)this.attributes.Push(); + if (a == null) { + a = new Attribute(); + this.attributes[this.attributes.Count-1] = a; + } + a.Reset(name, value, quotechar); + return a; + } + + [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")] + public void RemoveAttribute(string name) + { + for (int i = 0, n = this.attributes.Count; i < n; i++) + { + Attribute a = (Attribute)this.attributes[i]; + if (string.Equals(a.Name, name, StringComparison.OrdinalIgnoreCase)) + { + this.attributes.RemoveAt(i); + return; + } + } + } + public void CopyAttributes(Node n) { + for (int i = 0, len = n.attributes.Count; i < len; i++) { + Attribute a = (Attribute)n.attributes[i]; + Attribute na = this.AddAttribute(a.Name, a.Value, a.QuoteChar, false); + na.DtdType = a.DtdType; + } + } + + public int AttributeCount { + get { + return this.attributes.Count; + } + } + + public int GetAttribute(string name) { + for (int i = 0, n = this.attributes.Count; i < n; i++) { + Attribute a = (Attribute)this.attributes[i]; + if (string.Equals(a.Name, name, StringComparison.OrdinalIgnoreCase)) { + return i; + } + } + return -1; + } + + public Attribute GetAttribute(int i) { + if (i>=0 && i where we have special parsing rules. + PartialText, + PseudoStartTag, // we pushed a pseudo-start tag, need to continue with previous start tag. + Eof + } + + + /// + /// SgmlReader is an XmlReader API over any SGML document (including built in + /// support for HTML). + /// + public class SgmlReader : XmlReader + { + /// + /// The value returned when a namespace is queried and none has been specified. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1705", Justification = "SgmlReader's standards for constants are different to Microsoft's and in line with older C++ style constants naming conventions. Visually, constants using this style are more easily identifiable as such.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707", Justification = "SgmlReader's standards for constants are different to Microsoft's and in line with older C++ style constants naming conventions. Visually, constants using this style are more easily identifiable as such.")] + public const string UNDEFINED_NAMESPACE = "#unknown"; + + private SgmlDtd m_dtd; + private Entity m_current; + private State m_state; + private char m_partial; + private string m_endTag; + private HWStack m_stack; + private Node m_node; // current node (except for attributes) + // Attributes are handled separately using these members. + private Attribute m_a; + private int m_apos; // which attribute are we positioned on in the collection. + private Uri m_baseUri; + private StringBuilder m_sb; + private StringBuilder m_name; + private TextWriter m_log; + private bool m_foundRoot; + private bool m_ignoreDtd; + + // autoclose support + private Node m_newnode; + private int m_poptodepth; + private int m_rootCount; + private bool m_isHtml; + private string m_rootElementName; + + private string m_href; + private string m_errorLogFile; + private Entity m_lastError; + private string m_proxy; + private TextReader m_inputStream; + private string m_syslit; + private string m_pubid; + private string m_subset; + private string m_docType; + private WhitespaceHandling m_whitespaceHandling; + private CaseFolding m_folding = CaseFolding.None; + private bool m_stripDocType = true; + //private string m_startTag; + private Dictionary unknownNamespaces = new Dictionary(); + + /// + /// Initialises a new instance of the SgmlReader class. + /// + public SgmlReader() { + Init(); + } + + /// + /// Initialises a new instance of the SgmlReader class with an existing , which is NOT used. + /// + /// The nametable to use. + public SgmlReader(XmlNameTable nt) { + Init(); + } + + /// + /// Specify the SgmlDtd object directly. This allows you to cache the Dtd and share + /// it across multipl SgmlReaders. To load a DTD from a URL use the SystemLiteral property. + /// + public SgmlDtd Dtd + { + get + { + if (this.m_dtd == null) + { + LazyLoadDtd(this.m_baseUri); + } + + return this.m_dtd; + } + set + { + this.m_dtd = value; + } + } + + private void LazyLoadDtd(Uri baseUri) + { + if (this.m_dtd == null && !this.m_ignoreDtd) + { + if (string.IsNullOrEmpty(this.m_syslit)) + { + if (this.m_docType != null && StringUtilities.EqualsIgnoreCase(this.m_docType, "html")) + { + Assembly a = Assembly.GetExecutingAssembly(); + string name = a.FullName.Split(',')[0]+".Html.dtd"; + Stream stm = a.GetManifestResourceStream(name); + if (stm != null) + { + StreamReader sr = new StreamReader(stm); + this.m_dtd = SgmlDtd.Parse(baseUri, "HTML", sr, null, this.m_proxy, null); + } + } + } + else + { + if (baseUri != null) + { + baseUri = new Uri(baseUri, this.m_syslit); + } + else if (this.m_baseUri != null) + { + baseUri = new Uri(this.m_baseUri, this.m_syslit); + } + else + { +#if PORTABLE + throw new InvalidOperationException("Cannot load DTD without specifying base URI."); +#else + baseUri = new Uri(new Uri(Directory.GetCurrentDirectory() + "/"), this.m_syslit); +#endif + } + this.m_dtd = SgmlDtd.Parse(baseUri, this.m_docType, this.m_pubid, baseUri.AbsoluteUri, this.m_subset, this.m_proxy, null); } - } } - return string.Empty; - default: + if (this.m_dtd != null && this.m_dtd.Name != null) { - string value; - if ((NodeType == XmlNodeType.Attribute) || (NodeType == XmlNodeType.Element)) - { - - // check if a 'xmlns:prefix' attribute is defined - string key = "xmlns:" + prefix; - for (int i = this.m_stack.Count - 1; i > 0; --i) + switch(this.CaseFolding) { - Node node = this.m_stack[i] as Node; - if ((node != null) && (node.NodeType == XmlNodeType.Element)) - { - int index = node.GetAttribute(key); - if (index >= 0) - { - value = node.GetAttribute(index).Value; - if (value != null) - { - return value; - } - } - } + case CaseFolding.ToUpper: + this.m_rootElementName = this.m_dtd.Name.ToUpperInvariant(); + break; + case CaseFolding.ToLower: + this.m_rootElementName = this.m_dtd.Name.ToLowerInvariant(); + break; + default: + this.m_rootElementName = this.m_dtd.Name; + break; } - } - // check if we've seen this prefix before - if (!unknownNamespaces.TryGetValue(prefix, out value)) - { - if (unknownNamespaces.Count > 0) + this.m_isHtml = StringUtilities.EqualsIgnoreCase(this.m_dtd.Name, "html"); + } + } + + /// + /// The name of root element specified in the DOCTYPE tag. + /// + public string DocType + { + get + { + return this.m_docType; + } + set + { + this.m_docType = value; + } + } + + /// + /// The root element of the document. + /// + public string RootElementName + { + get + { + return m_rootElementName; + } + } + + /// + /// The PUBLIC identifier in the DOCTYPE tag + /// + public string PublicIdentifier + { + get + { + return this.m_pubid; + } + set + { + this.m_pubid = value; + } + } + + /// + /// The SYSTEM literal in the DOCTYPE tag identifying the location of the DTD. + /// + public string SystemLiteral + { + get + { + return this.m_syslit; + } + set + { + this.m_syslit = value; + } + } + + /// + /// The DTD internal subset in the DOCTYPE tag + /// + public string InternalSubset + { + get + { + return this.m_subset; + } + set + { + this.m_subset = value; + } + } + + /// + /// The input stream containing SGML data to parse. + /// You must specify this property or the Href property before calling Read(). + /// + public TextReader InputStream + { + get + { + return this.m_inputStream; + } + set + { + this.m_inputStream = value; + Init(); + } + } + + /// + /// Sometimes you need to specify a proxy server in order to load data via HTTP + /// from outside the firewall. For example: "itgproxy:80". + /// + public string WebProxy + { + get + { + return this.m_proxy; + } + set + { + this.m_proxy = value; + } + } + + /// + /// The base Uri is used to resolve relative Uri's like the SystemLiteral and + /// Href properties. This is a method because BaseURI is a read-only + /// property on the base XmlReader class. + /// + public void SetBaseUri(string uri) + { + this.m_baseUri = new Uri(uri); + } + + /// + /// Specify the location of the input SGML document as a URL. + /// + public string Href + { + get + { + return this.m_href; + } + set + { + this.m_href = value; + Init(); + if (this.m_baseUri == null) { - value = UNDEFINED_NAMESPACE + unknownNamespaces.Count.ToString(); + if (this.m_href.IndexOf("://") > 0) + { + this.m_baseUri = new Uri(this.m_href); + } + else + { +#if PORTABLE + throw new ArgumentException("Relative URLs are not supported."); +#else + this.m_baseUri = new Uri("file:///" + Directory.GetCurrentDirectory() + "//"); +#endif + } + } + } + } + + /// + /// Whether to strip out the DOCTYPE tag from the output (default true) + /// + public bool StripDocType + { + get + { + return this.m_stripDocType; + } + set + { + this.m_stripDocType = value; + } + } + + /// + /// Gets or sets a value indicating whether to ignore any DTD reference. + /// + /// true if DTD references should be ignored; otherwise, false. + public bool IgnoreDtd + { + get { return m_ignoreDtd; } + set { m_ignoreDtd = value; } + } + + /// + /// The case conversion behaviour while processing tags. + /// + public CaseFolding CaseFolding + { + get + { + return this.m_folding; + } + set + { + this.m_folding = value; + } + } + + /// + /// DTD validation errors are written to this stream. + /// + public TextWriter ErrorLog + { + get + { + return this.m_log; + } + set + { + this.m_log = value; + } + } + + /// + /// DTD validation errors are written to this log file. + /// +#if PORTABLE + [Obsolete("Not supported on the .NETPortable runtime.")] +#endif + public string ErrorLogFile + { +#if PORTABLE + get + { + throw new NotSupportedException(); + } + set + { + throw new NotSupportedException(); + } +#else + get + { + return this.m_errorLogFile; + } + set + { + this.m_errorLogFile = value; + this.m_log = new StreamWriter(value); + } +#endif + } + + private void Log(string msg, params string[] args) + { + if (ErrorLog != null) + { + string err = string.Format(CultureInfo.CurrentUICulture, msg, args); + if (this.m_lastError != this.m_current) + { + err = err + " " + this.m_current.Context(); + this.m_lastError = this.m_current; + ErrorLog.WriteLine("### Error:" + err); } else { - value = UNDEFINED_NAMESPACE; + string path = ""; + if (this.m_current.ResolvedUri != null) + { + path = this.m_current.ResolvedUri.AbsolutePath; + } + + ErrorLog.WriteLine("### Error in {0}#{1}, line {2}, position {3}: {4}", path, this.m_current.Name, this.m_current.Line, this.m_current.LinePosition, err); } - unknownNamespaces[prefix] = value; - } - return value; } } - } - } - /// - /// The prefix of the current node's name. - /// - public override string Prefix - { - get - { - string result = Name; - if (result != null) + private void Log(string msg, char ch) { - int colon = result.IndexOf(':'); - if (colon != -1) - { - result = result.Substring(0, colon); - } - else - { - result = string.Empty; - } - } - return result ?? string.Empty; - } - } - - /// - /// Whether the current node has a value or not. - /// - public override bool HasValue - { - get - { - if (this.m_state == State.Attr || this.m_state == State.AttrValue) - { - return true; + Log(msg, ch.ToString()); } - return (this.m_node.Value != null); - } - } - - /// - /// The value of the current node. - /// - public override string Value - { - get - { - if (this.m_state == State.Attr || this.m_state == State.AttrValue) + private void Init() { - return this.m_a.Value; + this.m_state = State.Initial; + this.m_stack = new HWStack(10); + this.m_node = Push(null, XmlNodeType.Document, null); + this.m_node.IsEmpty = false; + this.m_sb = new StringBuilder(); + this.m_name = new StringBuilder(); + this.m_poptodepth = 0; + this.m_current = null; + this.m_partial = '\0'; + this.m_endTag = null; + this.m_a = null; + this.m_apos = 0; + this.m_newnode = null; + this.m_rootCount = 0; + this.m_foundRoot = false; + this.unknownNamespaces.Clear(); } - return this.m_node.Value; - } - } - - /// - /// Gets the depth of the current node in the XML document. - /// - /// The depth of the current node in the XML document. - public override int Depth - { - get - { - if (this.m_state == State.Attr) + private Node Push(string name, XmlNodeType nt, string value) { - return this.m_stack.Count; - } - else if (this.m_state == State.AttrValue) - { - return this.m_stack.Count + 1; - } - - return this.m_stack.Count - 1; - } - } - - /// - /// Gets the base URI of the current node. - /// - /// The base URI of the current node. - public override string BaseURI - { - get - { - return this.m_baseUri == null ? "" : this.m_baseUri.AbsoluteUri; - } - } - - /// - /// Gets a value indicating whether the current node is an empty element (for example, <MyElement/>). - /// - public override bool IsEmptyElement - { - get - { - if (this.m_state == State.Markup || this.m_state == State.Attr || this.m_state == State.AttrValue) - { - return this.m_node.IsEmpty; - } - - return false; - } - } - - /// - /// Gets a value indicating whether the current node is an attribute that was generated from the default value defined in the DTD or schema. - /// - /// - /// true if the current node is an attribute whose value was generated from the default value defined in the DTD or - /// schema; false if the attribute value was explicitly set. - /// - public override bool IsDefault - { - get - { - if (this.m_state == State.Attr || this.m_state == State.AttrValue) - return this.m_a.IsDefault; - - return false; - } - } - - /// - /// Gets the quotation mark character used to enclose the value of an attribute node. - /// - /// The quotation mark character (" or ') used to enclose the value of an attribute node. - /// - /// This property applies only to an attribute node. - /// - public char QuoteChar - { - get - { - if (this.m_a != null) - return this.m_a.QuoteChar; - - return '\0'; - } - } - - /// - /// Gets the current xml:space scope. - /// - /// One of the values. If no xml:space scope exists, this property defaults to XmlSpace.None. - public override XmlSpace XmlSpace - { - get - { - for (int i = this.m_stack.Count - 1; i > 1; i--) - { - Node n = (Node)this.m_stack[i]; - XmlSpace xs = n.Space; - if (xs != XmlSpace.None) - return xs; - } - - return XmlSpace.None; - } - } - - /// - /// Gets the current xml:lang scope. - /// - /// The current xml:lang scope. - public override string XmlLang - { - get - { - for (int i = this.m_stack.Count - 1; i > 1; i--) - { - Node n = (Node)this.m_stack[i]; - string xmllang = n.XmlLang; - if (xmllang != null) - return xmllang; - } - - return string.Empty; - } - } - - /// - /// Specifies how white space is handled. - /// - public WhitespaceHandling WhitespaceHandling - { - get - { - return this.m_whitespaceHandling; - } - set - { - this.m_whitespaceHandling = value; - } - } - - /// - /// Gets the number of attributes on the current node. - /// - /// The number of attributes on the current node. - public override int AttributeCount - { - get - { - if (this.m_state == State.Attr || this.m_state == State.AttrValue) - //For compatibility with mono - return this.m_node.AttributeCount; - else if (this.m_node.NodeType == XmlNodeType.Element || this.m_node.NodeType == XmlNodeType.DocumentType) - return this.m_node.AttributeCount; - else - return 0; - } - } - - /// - /// Gets the value of an attribute with the specified . - /// - /// The name of the attribute to retrieve. - /// The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. - public override string GetAttribute(string name) - { - if (this.m_state != State.Attr && this.m_state != State.AttrValue) - { - int i = this.m_node.GetAttribute(name); - if (i >= 0) - return GetAttribute(i); - } - - return null; - } - - /// - /// Gets the value of the attribute with the specified and . - /// - /// The local name of the attribute. - /// The namespace URI of the attribute. - /// The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. This method does not move the reader. - public override string GetAttribute(string name, string namespaceURI) - { - return GetAttribute(name); // SGML has no namespaces. - } - - /// - /// Gets the value of the attribute with the specified index. - /// - /// The index of the attribute. - /// The value of the specified attribute. This method does not move the reader. - public override string GetAttribute(int i) - { - if (this.m_state != State.Attr && this.m_state != State.AttrValue) - { - Attribute a = this.m_node.GetAttribute(i); - if (a != null) - return a.Value; - } - - throw new ArgumentOutOfRangeException("i"); - } - - /// - /// Gets the value of the attribute with the specified index. - /// - /// The index of the attribute. - /// The value of the specified attribute. This method does not move the reader. - public override string this[int i] - { - get - { - return GetAttribute(i); - } - } - - /// - /// Gets the value of an attribute with the specified . - /// - /// The name of the attribute to retrieve. - /// The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. - public override string this[string name] - { - get - { - return GetAttribute(name); - } - } - - /// - /// Gets the value of the attribute with the specified and . - /// - /// The local name of the attribute. - /// The namespace URI of the attribute. - /// The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. This method does not move the reader. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1023", Justification = "This design is that of Microsoft's XmlReader class and overriding its method is merely continuing the same design.")] - public override string this[string name, string namespaceURI] - { - get - { - return GetAttribute(name, namespaceURI); - } - } - - /// - /// Moves to the atttribute with the specified . - /// - /// The qualified name of the attribute. - /// true if the attribute is found; otherwise, false. If false, the reader's position does not change. - public override bool MoveToAttribute(string name) - { - int i = this.m_node.GetAttribute(name); - if (i >= 0) - { - MoveToAttribute(i); - return true; - } - - return false; - } - - /// - /// Moves to the attribute with the specified and . - /// - /// The local name of the attribute. - /// The namespace URI of the attribute. - /// true if the attribute is found; otherwise, false. If false, the reader's position does not change. - public override bool MoveToAttribute(string name, string ns) - { - return MoveToAttribute(name); - } - - /// - /// Moves to the attribute with the specified index. - /// - /// The index of the attribute to move to. - public override void MoveToAttribute(int i) - { - Attribute a = this.m_node.GetAttribute(i); - if (a != null) - { - this.m_apos = i; - this.m_a = a; - //Make sure that AttrValue does not overwrite the preserved value - if (this.m_state != State.Attr && this.m_state != State.AttrValue) - { - this.m_node.CurrentState = this.m_state; //save current state. - } - - this.m_state = State.Attr; - return; - } - - throw new ArgumentOutOfRangeException("i"); - } - - /// - /// Moves to the first attribute. - /// - /// - public override bool MoveToFirstAttribute() - { - if (this.m_node.AttributeCount > 0) - { - MoveToAttribute(0); - return true; - } - - return false; - } - - /// - /// Moves to the next attribute. - /// - /// true if there is a next attribute; false if there are no more attributes. - /// - /// If the current node is an element node, this method is equivalent to . If returns true, - /// the reader moves to the next attribute; otherwise, the position of the reader does not change. - /// - public override bool MoveToNextAttribute() - { - if (this.m_state != State.Attr && this.m_state != State.AttrValue) - { - return MoveToFirstAttribute(); - } - else if (this.m_apos < this.m_node.AttributeCount - 1) - { - MoveToAttribute(this.m_apos + 1); - return true; - } - else - return false; - } - - /// - /// Moves to the element that contains the current attribute node. - /// - /// - /// true if the reader is positioned on an attribute (the reader moves to the element that owns the attribute); false if the reader is not positioned - /// on an attribute (the position of the reader does not change). - /// - public override bool MoveToElement() - { - if (this.m_state == State.Attr || this.m_state == State.AttrValue) - { - this.m_state = this.m_node.CurrentState; - this.m_a = null; - return true; - } - else - return (this.m_node.NodeType == XmlNodeType.Element); - } - - /// - /// Gets whether the content is HTML or not. - /// - public bool IsHtml - { - get - { - return this.m_isHtml; - } - } - - /// - /// Returns the encoding of the current entity. - /// - /// The encoding of the current entity. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024", Justification = "This method to get the encoding does not simply read a value, but potentially causes significant processing of the input stream.")] - public Encoding GetEncoding() - { - if (this.m_current == null) - { - OpenInput(); - } - - return this.m_current.Encoding; - } - - private void OpenInput() - { - LazyLoadDtd(this.m_baseUri); - - if (this.Href != null) - { - this.m_current = new Entity("#document", null, this.m_href, this.m_proxy); - } - else if (this.m_inputStream != null) - { - this.m_current = new Entity("#document", null, this.m_inputStream, this.m_proxy); - } - else - { - throw new InvalidOperationException("You must specify input either via Href or InputStream properties"); - } - - this.m_current.IsHtml = this.IsHtml; - this.m_current.Open(null, this.m_baseUri); - if (this.m_current.ResolvedUri != null) - this.m_baseUri = this.m_current.ResolvedUri; - - if (this.m_current.IsHtml && this.m_dtd == null) - { - this.m_docType = "HTML"; - LazyLoadDtd(this.m_baseUri); - } - } - - /// - /// Reads the next node from the stream. - /// - /// true if the next node was read successfully; false if there are no more nodes to read. - public override bool Read() - { - if (m_current == null) - { - OpenInput(); - } - - if (m_node.Simulated) - { - // return the next node - m_node.Simulated = false; - this.m_node = Top(); - this.m_state = this.m_node.CurrentState; - return true; - } - - bool foundnode = false; - while (!foundnode) - { - switch (this.m_state) - { - case State.Initial: - this.m_state = State.Markup; - this.m_current.ReadChar(); - goto case State.Markup; - case State.Eof: - if (this.m_current.Parent != null) + Node result = (Node)this.m_stack.Push(); + if (result == null) { - this.m_current.Close(); - this.m_current = this.m_current.Parent; - } - else - { - return false; - } - break; - case State.EndTag: - if (string.Equals(this.m_endTag, this.m_node.Name, StringComparison.OrdinalIgnoreCase)) - { - Pop(); // we're done! - this.m_state = State.Markup; - goto case State.Markup; - } - Pop(); // close one element - foundnode = true;// return another end element. - break; - case State.Markup: - if (this.m_node.IsEmpty) - { - Pop(); - } - foundnode = ParseMarkup(); - break; - case State.PartialTag: - Pop(); // remove text node. - this.m_state = State.Markup; - foundnode = ParseTag(this.m_partial); - break; - case State.PseudoStartTag: - foundnode = ParseStartTag('<'); - break; - case State.AutoClose: - Pop(); // close next node. - if (this.m_stack.Count <= this.m_poptodepth) - { - this.m_state = State.Markup; - if (this.m_newnode != null) - { - Push(this.m_newnode); // now we're ready to start the new node. - this.m_newnode = null; - this.m_state = State.Markup; - } - else if (this.m_node.NodeType == XmlNodeType.Document) - { - this.m_state = State.Eof; - goto case State.Eof; - } - } - foundnode = true; - break; - case State.CData: - foundnode = ParseCData(); - break; - case State.Attr: - goto case State.AttrValue; - case State.AttrValue: - this.m_state = State.Markup; - goto case State.Markup; - case State.Text: - Pop(); - goto case State.Markup; - case State.PartialText: - if (ParseText(this.m_current.Lastchar, false)) - { - this.m_node.NodeType = XmlNodeType.Whitespace; + result = new Node(); + this.m_stack[this.m_stack.Count - 1] = result; } - foundnode = true; - break; + result.Reset(name, nt, value); + this.m_node = result; + return result; } - if (foundnode && this.m_node.NodeType == XmlNodeType.Whitespace && this.m_whitespaceHandling == WhitespaceHandling.None) + private void SwapTopNodes() { - // strip out whitespace (caller is probably pretty printing the XML). - foundnode = false; - } - if (!foundnode && this.m_state == State.Eof && this.m_stack.Count > 1) - { - this.m_poptodepth = 1; - this.m_state = State.AutoClose; - this.m_node = Top(); - return true; - } - } - if (!m_foundRoot && (this.NodeType == XmlNodeType.Element || - this.NodeType == XmlNodeType.Text || - this.NodeType == XmlNodeType.CDATA)) - { - m_foundRoot = true; - if (this.IsHtml && (this.NodeType != XmlNodeType.Element || - !string.Equals(this.LocalName, "html", StringComparison.OrdinalIgnoreCase))) - { - // Simulate an HTML root element! - this.m_node.CurrentState = this.m_state; - Node root = Push("html", XmlNodeType.Element, null); - SwapTopNodes(); // make html the outer element. - this.m_node = root; - root.Simulated = true; - root.IsEmpty = false; - this.m_state = State.Markup; - //this.state = State.PseudoStartTag; - //this.startTag = name; - } - - return true; - } - - return true; - } - - private bool ParseMarkup() - { - char ch = this.m_current.Lastchar; - if (ch == '<') - { - ch = this.m_current.ReadChar(); - return ParseTag(ch); - } - else if (ch != Entity.EOF) - { - if (this.m_node.DtdType != null && this.m_node.DtdType.ContentModel.DeclaredContent == DeclaredContent.CDATA) - { - // e.g. SCRIPT or STYLE tags which contain unparsed character data. - this.m_partial = '\0'; - this.m_state = State.CData; - return false; - } - else if (ParseText(ch, true)) - { - this.m_node.NodeType = XmlNodeType.Whitespace; - } - - return true; - } - - this.m_state = State.Eof; - return false; - } - - private const string declterm = " \t\r\n><"; - private bool ParseTag(char ch) - { - if (ch == '%') - { - return ParseAspNet(); - } - else if (ch == '!') - { - ch = this.m_current.ReadChar(); - if (ch == '-') - { - return ParseComment(); - } - else if (ch == '[') - { - return ParseConditionalBlock(); - } - else if (ch != '_' && !char.IsLetter(ch)) - { - // perhaps it's one of those nasty office document hacks like '' - string value = this.m_current.ScanToEnd(this.m_sb, "Recovering", ">"); // skip it - Log("Ignoring invalid markup '"); - return false; - } - else - { - string name = this.m_current.ScanToken(this.m_sb, SgmlReader.declterm, false); - if (string.Equals(name, "DOCTYPE", StringComparison.OrdinalIgnoreCase)) - { - ParseDocType(); - - // In SGML DOCTYPE SYSTEM attribute is optional, but in XML it is required, - // therefore if there is no SYSTEM literal then add an empty one. - if (this.GetAttribute("SYSTEM") == null && this.GetAttribute("PUBLIC") != null) + int top = this.m_stack.Count - 1; + if (top > 0) { - this.m_node.AddAttribute("SYSTEM", "", '"', this.m_folding == CaseFolding.None); + Node n = (Node)this.m_stack[top - 1]; + this.m_stack[top - 1] = this.m_stack[top]; + this.m_stack[top] = n; + } + } + + private Node Push(Node n) + { + // we have to do a deep clone of the Node object because + // it is reused in the stack. + Node n2 = Push(n.Name, n.NodeType, n.Value); + n2.DtdType = n.DtdType; + n2.IsEmpty = n.IsEmpty; + n2.Space = n.Space; + n2.XmlLang = n.XmlLang; + n2.CurrentState = n.CurrentState; + n2.CopyAttributes(n); + this.m_node = n2; + return n2; + } + + private void Pop() + { + if (this.m_stack.Count > 1) + { + this.m_node = (Node)this.m_stack.Pop(); + } + } + + private Node Top() + { + int top = this.m_stack.Count - 1; + if (top > 0) + { + return (Node)this.m_stack[top]; } - if (m_stripDocType) + return null; + } + + /// + /// The node type of the node currently being parsed. + /// + public override XmlNodeType NodeType + { + get { - return false; + if (this.m_state == State.Attr) + { + return XmlNodeType.Attribute; + } + else if (this.m_state == State.AttrValue) + { + return XmlNodeType.Text; + } + else if (this.m_state == State.EndTag || this.m_state == State.AutoClose) + { + return XmlNodeType.EndElement; + } + + return this.m_node.NodeType; } - else + } + + /// + /// The name of the current node, if currently positioned on a node or attribute. + /// + public override string Name + { + get { - this.m_node.NodeType = XmlNodeType.DocumentType; - return true; + string result = null; + if (this.m_state == State.Attr) + { + result = XmlConvert.EncodeName(this.m_a.Name); + } + else if (this.m_state != State.AttrValue) + { + result = this.m_node.Name; + } + + return result; } - } - else - { - Log("Invalid declaration '"); // skip it - return false; - } - } - } - else if (ch == '?') - { - this.m_current.ReadChar();// consume the '?' character. - return ParsePI(); - } - else if (ch == '/') - { - return ParseEndTag(); - } - else - { - return ParseStartTag(ch); - } - } - - private string ScanName(string terminators) - { - string name = this.m_current.ScanToken(this.m_sb, terminators, false); - switch (this.m_folding) - { - case CaseFolding.ToUpper: - name = name.ToUpperInvariant(); - break; - case CaseFolding.ToLower: - name = name.ToLowerInvariant(); - break; - } - return name; - } - - private static bool VerifyName(string name) - { - try - { - XmlConvert.VerifyName(name); - return true; - } - catch (XmlException) - { - return false; - } - } - - private const string tagterm = " \t\r\n=/><"; - private const string aterm = " \t\r\n='\"/>"; - private const string avterm = " \t\r\n>"; - private bool ParseStartTag(char ch) - { - string name = null; - if (m_state != State.PseudoStartTag) - { - if (SgmlReader.tagterm.IndexOf(ch) >= 0) - { - this.m_sb.Length = 0; - this.m_sb.Append('<'); - this.m_state = State.PartialText; - return false; } - name = ScanName(SgmlReader.tagterm); - } - else - { - // TODO: Changes by mindtouch mean that this.startTag is never non-null. The effects of this need checking. - - //name = this.startTag; - m_state = State.Markup; - } - - Node n = Push(name, XmlNodeType.Element, null); - n.IsEmpty = false; - Validate(n); - ch = this.m_current.SkipWhitespace(); - while (ch != Entity.EOF && ch != '>') - { - if (ch == '/') + /// + /// The local name of the current node, if currently positioned on a node or attribute. + /// + public override string LocalName { - n.IsEmpty = true; - ch = this.m_current.ReadChar(); - if (ch != '>') - { - Log("Expected empty start tag '/>' sequence instead of '{0}'", ch); - this.m_current.ScanToEnd(null, "Recovering", ">"); - return false; - } - break; - } - else if (ch == '<') - { - Log("Start tag '{0}' is missing '>'", name); - break; - } - - string aname = ScanName(SgmlReader.aterm); - ch = this.m_current.SkipWhitespace(); - if (string.Equals(aname, ",", StringComparison.OrdinalIgnoreCase) || - string.Equals(aname, "=", StringComparison.OrdinalIgnoreCase) || - string.Equals(aname, ":", StringComparison.OrdinalIgnoreCase) || - string.Equals(aname, ";", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - string value = null; - char quote = '\0'; - if (ch == '=' || ch == '"' || ch == '\'') - { - if (ch == '=') - { - this.m_current.ReadChar(); - ch = this.m_current.SkipWhitespace(); - } - - if (ch == '\'' || ch == '\"') - { - quote = ch; - value = ScanLiteral(this.m_sb, ch); - } - else if (ch != '>') - { - string term = SgmlReader.avterm; - value = this.m_current.ScanToken(this.m_sb, term, false); - } - } - - if (ValidAttributeName(aname)) - { - Attribute a = n.AddAttribute(aname, value ?? aname, quote, this.m_folding == CaseFolding.None); - if (a == null) - { - Log("Duplicate attribute '{0}' ignored", aname); - } - else - { - ValidateAttribute(n, a); - } - } - - ch = this.m_current.SkipWhitespace(); - } - - if (ch == Entity.EOF) - { - this.m_current.Error("Unexpected EOF parsing start tag '{0}'", name); - } - else if (ch == '>') - { - this.m_current.ReadChar(); // consume '>' - } - - if (this.Depth == 1) - { - if (this.m_rootCount == 1) - { - // Hmmm, we found another root level tag, soooo, the only - // thing we can do to keep this a valid XML document is stop - this.m_state = State.Eof; - return false; - } - this.m_rootCount++; - } - - ValidateContent(n); - return true; - } - - private bool ParseEndTag() - { - this.m_state = State.EndTag; - this.m_current.ReadChar(); // consume '/' char. - string name = this.ScanName(SgmlReader.tagterm); - char ch = this.m_current.SkipWhitespace(); - if (ch != '>') - { - Log("Expected empty start tag '/>' sequence instead of '{0}'", ch); - this.m_current.ScanToEnd(null, "Recovering", ">"); - } - - this.m_current.ReadChar(); // consume '>' - - this.m_endTag = name; - - // Make sure there's a matching start tag for it. - bool caseInsensitive = (this.m_folding == CaseFolding.None); - this.m_node = (Node)this.m_stack[this.m_stack.Count - 1]; - for (int i = this.m_stack.Count - 1; i > 0; i--) - { - Node n = (Node)this.m_stack[i]; - if (string.Equals(n.Name, name, caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) - { - this.m_endTag = n.Name; - return true; - } - } - - Log("No matching start tag for ''", name); - this.m_state = State.Markup; - return false; - } - - private bool ParseAspNet() - { - string value = "<%" + this.m_current.ScanToEnd(this.m_sb, "AspNet", "%>") + "%>"; - Push(null, XmlNodeType.CDATA, value); - return true; - } - - private bool ParseComment() - { - char ch = this.m_current.ReadChar(); - if (ch != '-') - { - Log("Expecting comment '"); - - // Make sure it's a valid comment! - int i = value.IndexOf("--"); - - while (i >= 0) - { - int j = i + 2; - while (j < value.Length && value[j] == '-') - j++; - - if (i > 0) - { - value = value.Substring(0, i - 1) + "-" + value.Substring(j); - } - else - { - value = "-" + value.Substring(j); - } - - i = value.IndexOf("--"); - } - - if (value.Length > 0 && value[value.Length - 1] == '-') - { - value += " "; // '-' cannot be last character - } - - Push(null, XmlNodeType.Comment, value); - return true; - } - - private const string cdataterm = "\t\r\n[]<>"; - private bool ParseConditionalBlock() - { - char ch = m_current.ReadChar(); // skip '[' - ch = m_current.SkipWhitespace(); - string name = m_current.ScanToken(m_sb, cdataterm, false); - if (name.StartsWith("if ")) - { - // 'downlevel-revealed' comment (another atrocity of the IE team) - m_current.ScanToEnd(null, "CDATA", ">"); - return false; - } - else if (!string.Equals(name, "CDATA", StringComparison.OrdinalIgnoreCase)) - { - Log("Expecting CDATA but found '{0}'", name); - m_current.ScanToEnd(null, "CDATA", ">"); - return false; - } - else - { - ch = m_current.SkipWhitespace(); - if (ch != '[') - { - Log("Expecting '[' but found '{0}'", ch); - m_current.ScanToEnd(null, "CDATA", ">"); - return false; - } - - string value = m_current.ScanToEnd(m_sb, "CDATA", "]]>"); - - Push(null, XmlNodeType.CDATA, value); - return true; - } - } - - private const string dtterm = " \t\r\n>"; - private void ParseDocType() - { - char ch = this.m_current.SkipWhitespace(); - string name = this.ScanName(SgmlReader.dtterm); - Push(name, XmlNodeType.DocumentType, null); - ch = this.m_current.SkipWhitespace(); - if (ch != '>') - { - string subset = ""; - string pubid = ""; - string syslit = ""; - - if (ch != '[') - { - string token = this.m_current.ScanToken(this.m_sb, SgmlReader.dtterm, false); - if (string.Equals(token, "PUBLIC", StringComparison.OrdinalIgnoreCase)) - { - ch = this.m_current.SkipWhitespace(); - if (ch == '\"' || ch == '\'') + get { - pubid = this.m_current.ScanLiteral(this.m_sb, ch); - this.m_node.AddAttribute(token, pubid, ch, this.m_folding == CaseFolding.None); + string result = Name; + if (result != null) + { + int colon = result.IndexOf(':'); + if (colon != -1) + { + result = result.Substring(colon + 1); + } + } + return result; } - } - else if (!string.Equals(token, "SYSTEM", StringComparison.OrdinalIgnoreCase)) - { - Log("Unexpected token in DOCTYPE '{0}'", token); - this.m_current.ScanToEnd(null, "DOCTYPE", ">"); - } - ch = this.m_current.SkipWhitespace(); - if (ch == '\"' || ch == '\'') - { - token = "SYSTEM"; - syslit = this.m_current.ScanLiteral(this.m_sb, ch); - this.m_node.AddAttribute(token, syslit, ch, this.m_folding == CaseFolding.None); - } - ch = this.m_current.SkipWhitespace(); } - if (ch == '[') + /// + /// The namespace of the current node, if currently positioned on a node or attribute. + /// + /// + /// If not positioned on a node or attribute, is returned. + /// + [SuppressMessage("Microsoft.Performance", "CA1820", Justification="Cannot use IsNullOrEmpty in a switch statement and swapping the elegance of switch for a load of 'if's is not worth it.")] + public override string NamespaceURI { - subset = this.m_current.ScanToEnd(this.m_sb, "Internal Subset", "]"); - this.m_node.Value = subset; - } - - ch = this.m_current.SkipWhitespace(); - if (ch != '>') - { - Log("Expecting end of DOCTYPE tag, but found '{0}'", ch); - this.m_current.ScanToEnd(null, "DOCTYPE", ">"); - } - - if (this.m_dtd != null && !string.Equals(this.m_dtd.Name, name, StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException("DTD does not match document type"); - } - - this.m_docType = name; - this.m_pubid = pubid; - this.m_syslit = syslit; - this.m_subset = subset; - LazyLoadDtd(this.m_current.ResolvedUri); - } - - this.m_current.ReadChar(); - } - - private const string piterm = " \t\r\n?"; - private bool ParsePI() - { - string name = this.m_current.ScanToken(this.m_sb, SgmlReader.piterm, false); - string value = null; - if (this.m_current.Lastchar != '?') - { - // Notice this is not "?>". This is because Office generates bogus PI's that end with "/>". - value = this.m_current.ScanToEnd(this.m_sb, "Processing Instruction", ">"); - value = value.TrimEnd('/'); - } - else - { - // error recovery. - value = this.m_current.ScanToEnd(this.m_sb, "Processing Instruction", ">"); - } - - // check if the name has a prefix; if so, ignore it - int colon = name.IndexOf(':'); - if (colon > 0) - { - name = name.Substring(colon + 1); - } - - // skip xml declarations, since these are generated in the output instead. - if (!string.Equals(name, "xml", StringComparison.OrdinalIgnoreCase)) - { - Push(name, XmlNodeType.ProcessingInstruction, value); - return true; - } - - return false; - } - - private bool ParseText(char ch, bool newtext) - { - bool ws = !newtext || this.m_current.IsWhitespace; - if (newtext) - this.m_sb.Length = 0; - - //this.sb.Append(ch); - //ch = this.current.ReadChar(); - this.m_state = State.Text; - while (ch != Entity.EOF) - { - if (ch == '<') - { - ch = this.m_current.ReadChar(); - if (ch == '/' || ch == '!' || ch == '?' || char.IsLetter(ch)) - { - // Hit a tag, so return XmlNodeType.Text token - // and remember we partially started a new tag. - this.m_state = State.PartialTag; - this.m_partial = ch; - break; - } - else - { - // not a tag, so just proceed. - this.m_sb.Append('<'); - this.m_sb.Append(ch); - ws = false; - ch = this.m_current.ReadChar(); - } - } - else if (ch == '&') - { - ExpandEntity(this.m_sb, '<'); - ws = false; - ch = this.m_current.Lastchar; - } - else - { - if (!this.m_current.IsWhitespace) - ws = false; - this.m_sb.Append(ch); - ch = this.m_current.ReadChar(); - } - } - - string value = this.m_sb.ToString(); - Push(null, XmlNodeType.Text, value); - return ws; - } - - /// - /// Consumes and returns a literal block of text, expanding entities as it does so. - /// - /// The string builder to use. - /// The delimiter for the literal. - /// The consumed literal. - /// - /// This version is slightly different from in that - /// it also expands entities. - /// - private string ScanLiteral(StringBuilder sb, char quote) - { - sb.Length = 0; - char ch = this.m_current.ReadChar(); - while (ch != Entity.EOF && ch != quote && ch != '>') - { - if (ch == '&') - { - ExpandEntity(sb, quote); - ch = this.m_current.Lastchar; - } - else - { - sb.Append(ch); - ch = this.m_current.ReadChar(); - } - } - if (ch == quote) - { - this.m_current.ReadChar(); // consume end quote. - } - return sb.ToString(); - } - - private bool ParseCData() - { - // Like ParseText(), only it doesn't allow elements in the content. - // It allows comments and processing instructions and text only and - // text is not returned as text but CDATA (since it may contain angle brackets). - // And initial whitespace is ignored. It terminates when we hit the - // end tag for the current CDATA node (e.g. ). - bool ws = this.m_current.IsWhitespace; - this.m_sb.Length = 0; - char ch = this.m_current.Lastchar; - if (this.m_partial != '\0') - { - Pop(); // pop the CDATA - switch (this.m_partial) - { - case '!': - this.m_partial = ' '; // and pop the comment next time around - return ParseComment(); - case '?': - this.m_partial = ' '; // and pop the PI next time around - return ParsePI(); - case '/': - this.m_state = State.EndTag; - return true; // we are done! - case ' ': - break; // means we just needed to pop the Comment, PI or CDATA. - } - } - - // if this.partial == '!' then parse the comment and return - // if this.partial == '?' then parse the processing instruction and return. - while (ch != Entity.EOF) - { - if (ch == '<') - { - ch = this.m_current.ReadChar(); - if (ch == '!') - { - ch = this.m_current.ReadChar(); - if (ch == '-') + get { - // return what CDATA we have accumulated so far - // then parse the comment and return to here. - if (ws) - { - this.m_partial = ' '; // pop comment next time through - return ParseComment(); - } - else - { - // return what we've accumulated so far then come - // back in and parse the comment. - this.m_partial = '!'; - break; - } + // SGML has no namespaces, unless this turned out to be an xmlns attribute. + if (this.m_state == State.Attr && string.Equals(this.m_a.Name, "xmlns", StringComparison.OrdinalIgnoreCase)) + { + return "http://www.w3.org/2000/xmlns/"; + } + + string prefix = Prefix; + switch (Prefix) + { + case "xmlns": + return "http://www.w3.org/2000/xmlns/"; + case "xml": + return "http://www.w3.org/XML/1998/namespace"; + case null: // Should never occur since Prefix never returns null + case "": + if (NodeType == XmlNodeType.Attribute) + { + // attributes without a prefix are never in any namespace + return string.Empty; + } + else if (NodeType == XmlNodeType.Element) + { + // check if a 'xmlns:prefix' attribute is defined + for (int i = this.m_stack.Count - 1; i > 0; --i) + { + Node node = this.m_stack[i] as Node; + if ((node != null) && (node.NodeType == XmlNodeType.Element)) + { + int index = node.GetAttribute("xmlns"); + if (index >= 0) + { + string value = node.GetAttribute(index).Value; + if (value != null) + { + return value; + } + } + } + } + } + + return string.Empty; + default: { + string value; + if((NodeType == XmlNodeType.Attribute) || (NodeType == XmlNodeType.Element)) { + + // check if a 'xmlns:prefix' attribute is defined + string key = "xmlns:" + prefix; + for(int i = this.m_stack.Count - 1; i > 0; --i) { + Node node = this.m_stack[i] as Node; + if((node != null) && (node.NodeType == XmlNodeType.Element)) { + int index = node.GetAttribute(key); + if(index >= 0) { + value = node.GetAttribute(index).Value; + if(value != null) { + return value; + } + } + } + } + } + + // check if we've seen this prefix before + if(!unknownNamespaces.TryGetValue(prefix, out value)) { + if(unknownNamespaces.Count > 0) { + value = UNDEFINED_NAMESPACE + unknownNamespaces.Count.ToString(); + } else { + value = UNDEFINED_NAMESPACE; + } + unknownNamespaces[prefix] = value; + } + return value; + } + } } - else if (ch == '[') + } + + /// + /// The prefix of the current node's name. + /// + public override string Prefix + { + get { - // We are about to wrap this node as a CDATA block because of it's - // type in the DTD, but since we found a CDATA block in the input - // we have to parse it as a CDATA block, otherwise we will attempt - // to output nested CDATA blocks which of course is illegal. - if (this.ParseConditionalBlock()) - { - this.m_partial = ' '; + string result = Name; + if (result != null) + { + int colon = result.IndexOf(':'); + if(colon != -1) { + result = result.Substring(0, colon); + } else { + result = string.Empty; + } + } + return result ?? string.Empty; + } + } + + /// + /// Whether the current node has a value or not. + /// + public override bool HasValue + { + get + { + if (this.m_state == State.Attr || this.m_state == State.AttrValue) + { + return true; + } + + return (this.m_node.Value != null); + } + } + + /// + /// The value of the current node. + /// + public override string Value + { + get + { + if (this.m_state == State.Attr || this.m_state == State.AttrValue) + { + return this.m_a.Value; + } + + return this.m_node.Value; + } + } + + /// + /// Gets the depth of the current node in the XML document. + /// + /// The depth of the current node in the XML document. + public override int Depth + { + get + { + if (this.m_state == State.Attr) + { + return this.m_stack.Count; + } + else if (this.m_state == State.AttrValue) + { + return this.m_stack.Count + 1; + } + + return this.m_stack.Count - 1; + } + } + + /// + /// Gets the base URI of the current node. + /// + /// The base URI of the current node. + public override string BaseURI + { + get + { + return this.m_baseUri == null ? "" : this.m_baseUri.AbsoluteUri; + } + } + + /// + /// Gets a value indicating whether the current node is an empty element (for example, <MyElement/>). + /// + public override bool IsEmptyElement + { + get + { + if (this.m_state == State.Markup || this.m_state == State.Attr || this.m_state == State.AttrValue) + { + return this.m_node.IsEmpty; + } + + return false; + } + } + + /// + /// Gets a value indicating whether the current node is an attribute that was generated from the default value defined in the DTD or schema. + /// + /// + /// true if the current node is an attribute whose value was generated from the default value defined in the DTD or + /// schema; false if the attribute value was explicitly set. + /// + public override bool IsDefault + { + get + { + if (this.m_state == State.Attr || this.m_state == State.AttrValue) + return this.m_a.IsDefault; + + return false; + } + } + + /// + /// Gets the quotation mark character used to enclose the value of an attribute node. + /// + /// The quotation mark character (" or ') used to enclose the value of an attribute node. + /// + /// This property applies only to an attribute node. + /// +#if PORTABLE + public char QuoteChar +#else + public override char QuoteChar +#endif + { + get + { + if (this.m_a != null) + return this.m_a.QuoteChar; + + return '\0'; + } + } + + /// + /// Gets the current xml:space scope. + /// + /// One of the values. If no xml:space scope exists, this property defaults to XmlSpace.None. + public override XmlSpace XmlSpace + { + get + { + for (int i = this.m_stack.Count - 1; i > 1; i--) + { + Node n = (Node)this.m_stack[i]; + XmlSpace xs = n.Space; + if (xs != XmlSpace.None) + return xs; + } + + return XmlSpace.None; + } + } + + /// + /// Gets the current xml:lang scope. + /// + /// The current xml:lang scope. + public override string XmlLang + { + get + { + for (int i = this.m_stack.Count - 1; i > 1; i--) + { + Node n = (Node)this.m_stack[i]; + string xmllang = n.XmlLang; + if (xmllang != null) + return xmllang; + } + + return string.Empty; + } + } + + /// + /// Specifies how white space is handled. + /// + public WhitespaceHandling WhitespaceHandling + { + get + { + return this.m_whitespaceHandling; + } + set + { + this.m_whitespaceHandling = value; + } + } + + /// + /// Gets the number of attributes on the current node. + /// + /// The number of attributes on the current node. + public override int AttributeCount + { + get + { + if (this.m_state == State.Attr || this.m_state == State.AttrValue) + //For compatibility with mono + return this.m_node.AttributeCount; + else if (this.m_node.NodeType == XmlNodeType.Element || this.m_node.NodeType == XmlNodeType.DocumentType) + return this.m_node.AttributeCount; + else + return 0; + } + } + + /// + /// Gets the value of an attribute with the specified . + /// + /// The name of the attribute to retrieve. + /// The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. + public override string GetAttribute(string name) + { + if (this.m_state != State.Attr && this.m_state != State.AttrValue) + { + int i = this.m_node.GetAttribute(name); + if (i >= 0) + return GetAttribute(i); + } + + return null; + } + + /// + /// Gets the value of the attribute with the specified and . + /// + /// The local name of the attribute. + /// The namespace URI of the attribute. + /// The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. This method does not move the reader. + public override string GetAttribute(string name, string namespaceURI) + { + return GetAttribute(name); // SGML has no namespaces. + } + + /// + /// Gets the value of the attribute with the specified index. + /// + /// The index of the attribute. + /// The value of the specified attribute. This method does not move the reader. + public override string GetAttribute(int i) + { + if (this.m_state != State.Attr && this.m_state != State.AttrValue) + { + Attribute a = this.m_node.GetAttribute(i); + if (a != null) + return a.Value; + } + + throw new ArgumentOutOfRangeException("i"); + } + + /// + /// Gets the value of the attribute with the specified index. + /// + /// The index of the attribute. + /// The value of the specified attribute. This method does not move the reader. + public override string this[int i] + { + get + { + return GetAttribute(i); + } + } + + /// + /// Gets the value of an attribute with the specified . + /// + /// The name of the attribute to retrieve. + /// The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. + public override string this[string name] + { + get + { + return GetAttribute(name); + } + } + + /// + /// Gets the value of the attribute with the specified and . + /// + /// The local name of the attribute. + /// The namespace URI of the attribute. + /// The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. This method does not move the reader. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1023", Justification = "This design is that of Microsoft's XmlReader class and overriding its method is merely continuing the same design.")] + public override string this[string name, string namespaceURI] + { + get + { + return GetAttribute(name, namespaceURI); + } + } + + /// + /// Moves to the atttribute with the specified . + /// + /// The qualified name of the attribute. + /// true if the attribute is found; otherwise, false. If false, the reader's position does not change. + public override bool MoveToAttribute(string name) + { + int i = this.m_node.GetAttribute(name); + if (i >= 0) + { + MoveToAttribute(i); return true; - } + } - } - else + return false; + } + + /// + /// Moves to the attribute with the specified and . + /// + /// The local name of the attribute. + /// The namespace URI of the attribute. + /// true if the attribute is found; otherwise, false. If false, the reader's position does not change. + public override bool MoveToAttribute(string name, string ns) + { + return MoveToAttribute(name); + } + + /// + /// Moves to the attribute with the specified index. + /// + /// The index of the attribute to move to. + public override void MoveToAttribute(int i) + { + Attribute a = this.m_node.GetAttribute(i); + if (a != null) { - // not a comment, so ignore it and continue on. - this.m_sb.Append('<'); - this.m_sb.Append('!'); - this.m_sb.Append(ch); - ws = false; + this.m_apos = i; + this.m_a = a; + //Make sure that AttrValue does not overwrite the preserved value + if (this.m_state != State.Attr && this.m_state != State.AttrValue) + { + this.m_node.CurrentState = this.m_state; //save current state. + } + + this.m_state = State.Attr; + return; } - } - else if (ch == '?') - { - // processing instruction. - this.m_current.ReadChar();// consume the '?' character. - if (ws) + + throw new ArgumentOutOfRangeException("i"); + } + + /// + /// Moves to the first attribute. + /// + /// + public override bool MoveToFirstAttribute() + { + if (this.m_node.AttributeCount > 0) { - this.m_partial = ' '; // pop PI next time through - return ParsePI(); - } - else - { - this.m_partial = '?'; - break; - } - } - else if (ch == '/') - { - // see if this is the end tag for this CDATA node. - string temp = this.m_sb.ToString(); - if (ParseEndTag() && string.Equals(this.m_endTag, this.m_node.Name, StringComparison.OrdinalIgnoreCase)) - { - if (ws || string.IsNullOrEmpty(temp)) - { - // we are done! + MoveToAttribute(0); return true; - } - else - { - // return CDATA text then the end tag - this.m_partial = '/'; - this.m_sb.Length = 0; // restore buffer! - this.m_sb.Append(temp); - this.m_state = State.CData; - break; - } + } + + return false; + } + + /// + /// Moves to the next attribute. + /// + /// true if there is a next attribute; false if there are no more attributes. + /// + /// If the current node is an element node, this method is equivalent to . If returns true, + /// the reader moves to the next attribute; otherwise, the position of the reader does not change. + /// + public override bool MoveToNextAttribute() + { + if (this.m_state != State.Attr && this.m_state != State.AttrValue) + { + return MoveToFirstAttribute(); + } + else if (this.m_apos < this.m_node.AttributeCount - 1) + { + MoveToAttribute(this.m_apos + 1); + return true; + } + else + return false; + } + + /// + /// Moves to the element that contains the current attribute node. + /// + /// + /// true if the reader is positioned on an attribute (the reader moves to the element that owns the attribute); false if the reader is not positioned + /// on an attribute (the position of the reader does not change). + /// + public override bool MoveToElement() + { + if (this.m_state == State.Attr || this.m_state == State.AttrValue) + { + this.m_state = this.m_node.CurrentState; + this.m_a = null; + return true; + } + else + return (this.m_node.NodeType == XmlNodeType.Element); + } + + /// + /// Gets whether the content is HTML or not. + /// + public bool IsHtml + { + get + { + return this.m_isHtml; + } + } + + /// + /// Returns the encoding of the current entity. + /// + /// The encoding of the current entity. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024", Justification = "This method to get the encoding does not simply read a value, but potentially causes significant processing of the input stream.")] + public Encoding GetEncoding() + { + if (this.m_current == null) + { + OpenInput(); + } + + return this.m_current.Encoding; + } + + private void OpenInput() + { + LazyLoadDtd(this.m_baseUri); + + if (this.Href != null) + { + this.m_current = new Entity("#document", null, this.m_href, this.m_proxy); + } + else if (this.m_inputStream != null) + { + this.m_current = new Entity("#document", null, this.m_inputStream, this.m_proxy); } else { - // wrong end tag, so continue on. - this.m_sb.Length = 0; // restore buffer! - this.m_sb.Append(temp); - this.m_sb.Append(""); - ws = false; - - // NOTE (steveb): we have one character in the buffer that we need to process next - ch = this.m_current.Lastchar; - continue; + throw new InvalidOperationException("You must specify input either via Href or InputStream properties"); } - } - else - { - // must be just part of the CDATA block, so proceed. - this.m_sb.Append('<'); - this.m_sb.Append(ch); - ws = false; - } - } - else - { - if (!this.m_current.IsWhitespace && ws) - ws = false; - this.m_sb.Append(ch); - } - ch = this.m_current.ReadChar(); - } + this.m_current.IsHtml = this.IsHtml; + this.m_current.Open(null, this.m_baseUri); + if (this.m_current.ResolvedUri != null) + this.m_baseUri = this.m_current.ResolvedUri; - // NOTE (steveb): check if we reached EOF, which means it's over - if (ch == Entity.EOF) - { - this.m_state = State.Eof; - return false; - } - - string value = this.m_sb.ToString(); - - // NOTE (steveb): replace any nested CDATA sections endings - value = value.Replace("", string.Empty); - value = value.Replace("/**/", string.Empty); - - Push(null, XmlNodeType.CDATA, value); - if (this.m_partial == '\0') - this.m_partial = ' ';// force it to pop this CDATA next time in. - - return true; - } - - private void ExpandEntity(StringBuilder sb, char terminator) - { - char ch = this.m_current.ReadChar(); - if (ch == '#') - { - string charent = this.m_current.ExpandCharEntity(); - sb.Append(charent); - ch = this.m_current.Lastchar; - } - else - { - this.m_name.Length = 0; - while (ch != Entity.EOF && - (char.IsLetter(ch) || ch == '_' || ch == '-') || ((this.m_name.Length > 0) && char.IsDigit(ch))) - { - this.m_name.Append(ch); - ch = this.m_current.ReadChar(); - } - string name = this.m_name.ToString(); - - // TODO (steveb): don't lookup amp, gt, lt, quote - switch (name) - { - case "amp": - sb.Append("&"); - if (ch != terminator && ch != '&' && ch != Entity.EOF) - ch = this.m_current.ReadChar(); - return; - case "lt": - sb.Append("<"); - if (ch != terminator && ch != '&' && ch != Entity.EOF) - ch = this.m_current.ReadChar(); - return; - case "gt": - sb.Append(">"); - if (ch != terminator && ch != '&' && ch != Entity.EOF) - ch = this.m_current.ReadChar(); - return; - case "quot": - sb.Append("\""); - if (ch != terminator && ch != '&' && ch != Entity.EOF) - ch = this.m_current.ReadChar(); - return; - case "apos": - sb.Append("'"); - if (ch != terminator && ch != '&' && ch != Entity.EOF) - ch = this.m_current.ReadChar(); - return; - } - - if (this.m_dtd != null && !string.IsNullOrEmpty(name)) - { - Entity e = (Entity)this.m_dtd.FindEntity(name); - if (e != null) - { - if (e.IsInternal) + if (this.m_current.IsHtml && this.m_dtd == null) + { + this.m_docType = "HTML"; + LazyLoadDtd(this.m_baseUri); + } + } + + /// + /// Reads the next node from the stream. + /// + /// true if the next node was read successfully; false if there are no more nodes to read. + public override bool Read() + { + if (m_current == null) + { + OpenInput(); + } + + if (m_node.Simulated) + { + // return the next node + m_node.Simulated = false; + this.m_node = Top(); + this.m_state = this.m_node.CurrentState; + return true; + } + + bool foundnode = false; + while (!foundnode) + { + switch (this.m_state) + { + case State.Initial: + this.m_state = State.Markup; + this.m_current.ReadChar(); + goto case State.Markup; + case State.Eof: + if (this.m_current.Parent != null) + { + this.m_current.Close(); + this.m_current = this.m_current.Parent; + } + else + { + return false; + } + break; + case State.EndTag: + if (string.Equals(this.m_endTag, this.m_node.Name, StringComparison.OrdinalIgnoreCase)) + { + Pop(); // we're done! + this.m_state = State.Markup; + goto case State.Markup; + } + Pop(); // close one element + foundnode = true;// return another end element. + break; + case State.Markup: + if (this.m_node.IsEmpty) + { + Pop(); + } + foundnode = ParseMarkup(); + break; + case State.PartialTag: + Pop(); // remove text node. + this.m_state = State.Markup; + foundnode = ParseTag(this.m_partial); + break; + case State.PseudoStartTag: + foundnode = ParseStartTag('<'); + break; + case State.AutoClose: + Pop(); // close next node. + if (this.m_stack.Count <= this.m_poptodepth) + { + this.m_state = State.Markup; + if (this.m_newnode != null) + { + Push(this.m_newnode); // now we're ready to start the new node. + this.m_newnode = null; + this.m_state = State.Markup; + } + else if (this.m_node.NodeType == XmlNodeType.Document) + { + this.m_state = State.Eof; + goto case State.Eof; + } + } + foundnode = true; + break; + case State.CData: + foundnode = ParseCData(); + break; + case State.Attr: + goto case State.AttrValue; + case State.AttrValue: + this.m_state = State.Markup; + goto case State.Markup; + case State.Text: + Pop(); + goto case State.Markup; + case State.PartialText: + if (ParseText(this.m_current.Lastchar, false)) + { + this.m_node.NodeType = XmlNodeType.Whitespace; + } + + foundnode = true; + break; + } + + if (foundnode && this.m_node.NodeType == XmlNodeType.Whitespace && this.m_whitespaceHandling == WhitespaceHandling.None) + { + // strip out whitespace (caller is probably pretty printing the XML). + foundnode = false; + } + if (!foundnode && this.m_state == State.Eof && this.m_stack.Count > 1) + { + this.m_poptodepth = 1; + this.m_state = State.AutoClose; + this.m_node = Top(); + return true; + } + } + if (!m_foundRoot && (this.NodeType == XmlNodeType.Element || + this.NodeType == XmlNodeType.Text || + this.NodeType == XmlNodeType.CDATA)) + { + m_foundRoot = true; + if (this.IsHtml && (this.NodeType != XmlNodeType.Element || + !string.Equals(this.LocalName, "html", StringComparison.OrdinalIgnoreCase))) + { + // Simulate an HTML root element! + this.m_node.CurrentState = this.m_state; + Node root = Push("html", XmlNodeType.Element, null); + SwapTopNodes(); // make html the outer element. + this.m_node = root; + root.Simulated = true; + root.IsEmpty = false; + this.m_state = State.Markup; + //this.state = State.PseudoStartTag; + //this.startTag = name; + } + + return true; + } + + return true; + } + + private bool ParseMarkup() + { + char ch = this.m_current.Lastchar; + if (ch == '<') { - sb.Append(e.Literal); - if (ch != terminator && ch != '&' && ch != Entity.EOF) ch = this.m_current.ReadChar(); + return ParseTag(ch); + } + else if (ch != Entity.EOF) + { + if (this.m_node.DtdType != null && this.m_node.DtdType.ContentModel.DeclaredContent == DeclaredContent.CDATA) + { + // e.g. SCRIPT or STYLE tags which contain unparsed character data. + this.m_partial = '\0'; + this.m_state = State.CData; + return false; + } + else if (ParseText(ch, true)) + { + this.m_node.NodeType = XmlNodeType.Whitespace; + } - return; + return true; + } + + this.m_state = State.Eof; + return false; + } + + private const string declterm = " \t\r\n><"; + private bool ParseTag(char ch) + { + if (ch == '%') + { + return ParseAspNet(); + } + else if (ch == '!') + { + ch = this.m_current.ReadChar(); + if (ch == '-') + { + return ParseComment(); + } + else if (ch == '[') + { + return ParseConditionalBlock(); + } + else if (ch != '_' && !char.IsLetter(ch)) + { + // perhaps it's one of those nasty office document hacks like '' + string value = this.m_current.ScanToEnd(this.m_sb, "Recovering", ">"); // skip it + Log("Ignoring invalid markup '"); + return false; + } + else + { + string name = this.m_current.ScanToken(this.m_sb, SgmlReader.declterm, false); + if (string.Equals(name, "DOCTYPE", StringComparison.OrdinalIgnoreCase)) + { + ParseDocType(); + + // In SGML DOCTYPE SYSTEM attribute is optional, but in XML it is required, + // therefore if there is no SYSTEM literal then add an empty one. + if (this.GetAttribute("SYSTEM") == null && this.GetAttribute("PUBLIC") != null) + { + this.m_node.AddAttribute("SYSTEM", "", '"', this.m_folding == CaseFolding.None); + } + + if (m_stripDocType) + { + return false; + } + else + { + this.m_node.NodeType = XmlNodeType.DocumentType; + return true; + } + } + else + { + Log("Invalid declaration '"); // skip it + return false; + } + } + } + else if (ch == '?') + { + this.m_current.ReadChar();// consume the '?' character. + return ParsePI(); + } + else if (ch == '/') + { + return ParseEndTag(); } else { - Entity ex = new Entity(name, e.PublicId, e.Uri, this.m_current.Proxy); - e.Open(this.m_current, new Uri(e.Uri)); - this.m_current = ex; - this.m_current.ReadChar(); - return; + return ParseStartTag(ch); } - } - else - { - Log("Undefined entity '{0}'", name); - } } - // Entity is not defined, so just keep it in with the rest of the - // text. - sb.Append("&"); - sb.Append(name); - if (ch != terminator && ch != '&' && ch != Entity.EOF) + + private string ScanName(string terminators) { - sb.Append(ch); - ch = this.m_current.ReadChar(); + string name = this.m_current.ScanToken(this.m_sb, terminators, false); + switch (this.m_folding) + { + case CaseFolding.ToUpper: + name = name.ToUpperInvariant(); + break; + case CaseFolding.ToLower: + name = name.ToLowerInvariant(); + break; + } + return name; } - } - } - /// - /// Gets a value indicating whether the reader is positioned at the end of the stream. - /// - /// true if the reader is positioned at the end of the stream; otherwise, false. - public override bool EOF - { - get - { - return this.m_state == State.Eof; - } - } - - /// - /// Changes the to Closed. - /// - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (!disposing) - { - return; - } - - if (this.m_current != null) - { - this.m_current.Close(); - this.m_current = null; - } - - if (this.m_log != null) - { - this.m_log.Dispose(); - this.m_log = null; - } - } - - /// - /// Gets the state of the reader. - /// - /// One of the ReadState values. - public override ReadState ReadState - { - get - { - if (this.m_state == State.Initial) - return ReadState.Initial; - else if (this.m_state == State.Eof) - return ReadState.EndOfFile; - else - return ReadState.Interactive; - } - } - - /// - /// Reads the contents of an element or text node as a string. - /// - /// The contents of the element or an empty string. - public override string ReadContentAsString() - { - if (this.m_node.NodeType == XmlNodeType.Element) - { - this.m_sb.Length = 0; - while (Read()) + private static bool VerifyName(string name) { - switch (this.NodeType) - { - case XmlNodeType.CDATA: - case XmlNodeType.SignificantWhitespace: - case XmlNodeType.Whitespace: - case XmlNodeType.Text: - this.m_sb.Append(this.m_node.Value); - break; - default: - return this.m_sb.ToString(); - } + try + { + XmlConvert.VerifyName(name); + return true; + } + catch (XmlException) + { + return false; + } } - return this.m_sb.ToString(); - } + private const string tagterm = " \t\r\n=/><"; + private const string aterm = " \t\r\n='\"/>"; + private const string avterm = " \t\r\n>"; + private bool ParseStartTag(char ch) + { + string name = null; + if (m_state != State.PseudoStartTag) + { + if (SgmlReader.tagterm.IndexOf(ch) >= 0) + { + this.m_sb.Length = 0; + this.m_sb.Append('<'); + this.m_state = State.PartialText; + return false; + } - return this.m_node.Value; - } + name = ScanName(SgmlReader.tagterm); + } + else + { + // TODO: Changes by mindtouch mean that this.startTag is never non-null. The effects of this need checking. - /// - /// Reads all the content, including markup, as a string. - /// - /// - /// All the XML content, including markup, in the current node. If the current node has no children, - /// an empty string is returned. If the current node is neither an element nor attribute, an empty - /// string is returned. - /// - public override string ReadInnerXml() - { - StringWriter sw = new StringWriter(CultureInfo.InvariantCulture); - XmlWriter xw = XmlWriter.Create(sw, new XmlWriterSettings { Indent = true, IndentChars = " " }); - switch (this.NodeType) - { - case XmlNodeType.Element: - Read(); - while (!this.EOF && this.NodeType != XmlNodeType.EndElement) - { + //name = this.startTag; + m_state = State.Markup; + } + + Node n = Push(name, XmlNodeType.Element, null); + n.IsEmpty = false; + Validate(n); + ch = this.m_current.SkipWhitespace(); + while (ch != Entity.EOF && ch != '>') + { + if (ch == '/') + { + n.IsEmpty = true; + ch = this.m_current.ReadChar(); + if (ch != '>') + { + Log("Expected empty start tag '/>' sequence instead of '{0}'", ch); + this.m_current.ScanToEnd(null, "Recovering", ">"); + return false; + } + break; + } + else if (ch == '<') + { + Log("Start tag '{0}' is missing '>'", name); + break; + } + + string aname = ScanName(SgmlReader.aterm); + ch = this.m_current.SkipWhitespace(); + if (string.Equals(aname, ",", StringComparison.OrdinalIgnoreCase) || + string.Equals(aname, "=", StringComparison.OrdinalIgnoreCase) || + string.Equals(aname, ":", StringComparison.OrdinalIgnoreCase) || + string.Equals(aname, ";", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + string value = null; + char quote = '\0'; + if (ch == '=' || ch == '"' || ch == '\'') + { + if (ch == '=' ) + { + this.m_current.ReadChar(); + ch = this.m_current.SkipWhitespace(); + } + + if (ch == '\'' || ch == '\"') + { + quote = ch; + value = ScanLiteral(this.m_sb, ch); + } + else if (ch != '>') + { + string term = SgmlReader.avterm; + value = this.m_current.ScanToken(this.m_sb, term, false); + } + } + + if (ValidAttributeName(aname)) + { + Attribute a = n.AddAttribute(aname, value ?? aname, quote, this.m_folding == CaseFolding.None); + if (a == null) + { + Log("Duplicate attribute '{0}' ignored", aname); + } + else + { + ValidateAttribute(n, a); + } + } + + ch = this.m_current.SkipWhitespace(); + } + + if (ch == Entity.EOF) + { + this.m_current.Error("Unexpected EOF parsing start tag '{0}'", name); + } + else if (ch == '>') + { + this.m_current.ReadChar(); // consume '>' + } + + if (this.Depth == 1) + { + if (this.m_rootCount == 1) + { + // Hmmm, we found another root level tag, soooo, the only + // thing we can do to keep this a valid XML document is stop + this.m_state = State.Eof; + return false; + } + this.m_rootCount++; + } + + ValidateContent(n); + return true; + } + + private bool ParseEndTag() + { + this.m_state = State.EndTag; + this.m_current.ReadChar(); // consume '/' char. + string name = this.ScanName(SgmlReader.tagterm); + char ch = this.m_current.SkipWhitespace(); + if (ch != '>') + { + Log("Expected empty start tag '/>' sequence instead of '{0}'", ch); + this.m_current.ScanToEnd(null, "Recovering", ">"); + } + + this.m_current.ReadChar(); // consume '>' + + this.m_endTag = name; + + // Make sure there's a matching start tag for it. + bool caseInsensitive = (this.m_folding == CaseFolding.None); + this.m_node = (Node)this.m_stack[this.m_stack.Count - 1]; + for (int i = this.m_stack.Count - 1; i > 0; i--) + { + Node n = (Node)this.m_stack[i]; + if (string.Equals(n.Name, name, caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) + { + this.m_endTag = n.Name; + return true; + } + } + + Log("No matching start tag for ''", name); + this.m_state = State.Markup; + return false; + } + + private bool ParseAspNet() + { + string value = "<%" + this.m_current.ScanToEnd(this.m_sb, "AspNet", "%>") + "%>"; + Push(null, XmlNodeType.CDATA, value); + return true; + } + + private bool ParseComment() + { + char ch = this.m_current.ReadChar(); + if (ch != '-') + { + Log("Expecting comment '"); + + // Make sure it's a valid comment! + int i = value.IndexOf("--"); + + while (i >= 0) + { + int j = i + 2; + while (j < value.Length && value[j] == '-') + j++; + + if (i > 0) + { + value = value.Substring(0, i - 1) + "-" + value.Substring(j); + } + else + { + value = "-" + value.Substring(j); + } + + i = value.IndexOf("--"); + } + + if (value.Length > 0 && value[value.Length - 1] == '-') + { + value += " "; // '-' cannot be last character + } + + Push(null, XmlNodeType.Comment, value); + return true; + } + + private const string cdataterm = "\t\r\n[]<>"; + private bool ParseConditionalBlock() + { + char ch = m_current.ReadChar(); // skip '[' + ch = m_current.SkipWhitespace(); + string name = m_current.ScanToken(m_sb, cdataterm, false); + if (name.StartsWith("if ")) + { + // 'downlevel-revealed' comment (another atrocity of the IE team) + m_current.ScanToEnd(null, "CDATA", ">"); + return false; + } + else if (!string.Equals(name, "CDATA", StringComparison.OrdinalIgnoreCase)) + { + Log("Expecting CDATA but found '{0}'", name); + m_current.ScanToEnd(null, "CDATA", ">"); + return false; + } + else + { + ch = m_current.SkipWhitespace(); + if (ch != '[') + { + Log("Expecting '[' but found '{0}'", ch); + m_current.ScanToEnd(null, "CDATA", ">"); + return false; + } + + string value = m_current.ScanToEnd(m_sb, "CDATA", "]]>"); + + Push(null, XmlNodeType.CDATA, value); + return true; + } + } + + private const string dtterm = " \t\r\n>"; + private void ParseDocType() + { + char ch = this.m_current.SkipWhitespace(); + string name = this.ScanName(SgmlReader.dtterm); + Push(name, XmlNodeType.DocumentType, null); + ch = this.m_current.SkipWhitespace(); + if (ch != '>') + { + string subset = ""; + string pubid = ""; + string syslit = ""; + + if (ch != '[') + { + string token = this.m_current.ScanToken(this.m_sb, SgmlReader.dtterm, false); + if (string.Equals(token, "PUBLIC", StringComparison.OrdinalIgnoreCase)) + { + ch = this.m_current.SkipWhitespace(); + if (ch == '\"' || ch == '\'') + { + pubid = this.m_current.ScanLiteral(this.m_sb, ch); + this.m_node.AddAttribute(token, pubid, ch, this.m_folding == CaseFolding.None); + } + } + else if (!string.Equals(token, "SYSTEM", StringComparison.OrdinalIgnoreCase)) + { + Log("Unexpected token in DOCTYPE '{0}'", token); + this.m_current.ScanToEnd(null, "DOCTYPE", ">"); + } + ch = this.m_current.SkipWhitespace(); + if (ch == '\"' || ch == '\'') + { + token = "SYSTEM"; + syslit = this.m_current.ScanLiteral(this.m_sb, ch); + this.m_node.AddAttribute(token, syslit, ch, this.m_folding == CaseFolding.None); + } + ch = this.m_current.SkipWhitespace(); + } + + if (ch == '[') + { + subset = this.m_current.ScanToEnd(this.m_sb, "Internal Subset", "]"); + this.m_node.Value = subset; + } + + ch = this.m_current.SkipWhitespace(); + if (ch != '>') + { + Log("Expecting end of DOCTYPE tag, but found '{0}'", ch); + this.m_current.ScanToEnd(null, "DOCTYPE", ">"); + } + + if (this.m_dtd != null && !string.Equals(this.m_dtd.Name, name, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("DTD does not match document type"); + } + + this.m_docType = name; + this.m_pubid = pubid; + this.m_syslit = syslit; + this.m_subset = subset; + LazyLoadDtd(this.m_current.ResolvedUri); + } + + this.m_current.ReadChar(); + } + + private const string piterm = " \t\r\n?"; + private bool ParsePI() + { + string name = this.m_current.ScanToken(this.m_sb, SgmlReader.piterm, false); + string value = null; + if (this.m_current.Lastchar != '?') + { + // Notice this is not "?>". This is because Office generates bogus PI's that end with "/>". + value = this.m_current.ScanToEnd(this.m_sb, "Processing Instruction", ">"); + value = value.TrimEnd('/'); + } + else + { + // error recovery. + value = this.m_current.ScanToEnd(this.m_sb, "Processing Instruction", ">"); + } + + // check if the name has a prefix; if so, ignore it + int colon = name.IndexOf(':'); + if(colon > 0) { + name = name.Substring(colon + 1); + } + + // skip xml declarations, since these are generated in the output instead. + if (!string.Equals(name, "xml", StringComparison.OrdinalIgnoreCase)) + { + Push(name, XmlNodeType.ProcessingInstruction, value); + return true; + } + + return false; + } + + private bool ParseText(char ch, bool newtext) + { + bool ws = !newtext || this.m_current.IsWhitespace; + if (newtext) + this.m_sb.Length = 0; + + //this.sb.Append(ch); + //ch = this.current.ReadChar(); + this.m_state = State.Text; + while (ch != Entity.EOF) + { + if (ch == '<') + { + ch = this.m_current.ReadChar(); + if (ch == '/' || ch == '!' || ch == '?' || char.IsLetter(ch)) + { + // Hit a tag, so return XmlNodeType.Text token + // and remember we partially started a new tag. + this.m_state = State.PartialTag; + this.m_partial = ch; + break; + } + else + { + // not a tag, so just proceed. + this.m_sb.Append('<'); + this.m_sb.Append(ch); + ws = false; + ch = this.m_current.ReadChar(); + } + } + else if (ch == '&') + { + ExpandEntity(this.m_sb, '<'); + ws = false; + ch = this.m_current.Lastchar; + } + else + { + if (!this.m_current.IsWhitespace) + ws = false; + this.m_sb.Append(ch); + ch = this.m_current.ReadChar(); + } + } + + string value = this.m_sb.ToString(); + Push(null, XmlNodeType.Text, value); + return ws; + } + + /// + /// Consumes and returns a literal block of text, expanding entities as it does so. + /// + /// The string builder to use. + /// The delimiter for the literal. + /// The consumed literal. + /// + /// This version is slightly different from in that + /// it also expands entities. + /// + private string ScanLiteral(StringBuilder sb, char quote) + { + sb.Length = 0; + char ch = this.m_current.ReadChar(); + while (ch != Entity.EOF && ch != quote && ch != '>') + { + if (ch == '&') + { + ExpandEntity(sb, quote); + ch = this.m_current.Lastchar; + } + else + { + sb.Append(ch); + ch = this.m_current.ReadChar(); + } + } + if(ch == quote) { + this.m_current.ReadChar(); // consume end quote. + } + return sb.ToString(); + } + + private bool ParseCData() + { + // Like ParseText(), only it doesn't allow elements in the content. + // It allows comments and processing instructions and text only and + // text is not returned as text but CDATA (since it may contain angle brackets). + // And initial whitespace is ignored. It terminates when we hit the + // end tag for the current CDATA node (e.g. ). + bool ws = this.m_current.IsWhitespace; + this.m_sb.Length = 0; + char ch = this.m_current.Lastchar; + if (this.m_partial != '\0') + { + Pop(); // pop the CDATA + switch (this.m_partial) + { + case '!': + this.m_partial = ' '; // and pop the comment next time around + return ParseComment(); + case '?': + this.m_partial = ' '; // and pop the PI next time around + return ParsePI(); + case '/': + this.m_state = State.EndTag; + return true; // we are done! + case ' ': + break; // means we just needed to pop the Comment, PI or CDATA. + } + } + + // if this.partial == '!' then parse the comment and return + // if this.partial == '?' then parse the processing instruction and return. + while (ch != Entity.EOF) + { + if (ch == '<') + { + ch = this.m_current.ReadChar(); + if (ch == '!') + { + ch = this.m_current.ReadChar(); + if (ch == '-') + { + // return what CDATA we have accumulated so far + // then parse the comment and return to here. + if (ws) + { + this.m_partial = ' '; // pop comment next time through + return ParseComment(); + } + else + { + // return what we've accumulated so far then come + // back in and parse the comment. + this.m_partial = '!'; + break; + } +#if FIX + } else if (ch == '['){ + // We are about to wrap this node as a CDATA block because of it's + // type in the DTD, but since we found a CDATA block in the input + // we have to parse it as a CDATA block, otherwise we will attempt + // to output nested CDATA blocks which of course is illegal. + if (this.ParseConditionalBlock()){ + this.partial = ' '; + return true; + } +#endif + } + else + { + // not a comment, so ignore it and continue on. + this.m_sb.Append('<'); + this.m_sb.Append('!'); + this.m_sb.Append(ch); + ws = false; + } + } + else if (ch == '?') + { + // processing instruction. + this.m_current.ReadChar();// consume the '?' character. + if (ws) + { + this.m_partial = ' '; // pop PI next time through + return ParsePI(); + } + else + { + this.m_partial = '?'; + break; + } + } + else if (ch == '/') + { + // see if this is the end tag for this CDATA node. + string temp = this.m_sb.ToString(); + if (ParseEndTag() && string.Equals(this.m_endTag, this.m_node.Name, StringComparison.OrdinalIgnoreCase)) + { + if (ws || string.IsNullOrEmpty(temp)) + { + // we are done! + return true; + } + else + { + // return CDATA text then the end tag + this.m_partial = '/'; + this.m_sb.Length = 0; // restore buffer! + this.m_sb.Append(temp); + this.m_state = State.CData; + break; + } + } + else + { + // wrong end tag, so continue on. + this.m_sb.Length = 0; // restore buffer! + this.m_sb.Append(temp); + this.m_sb.Append(""); + ws = false; + + // NOTE (steveb): we have one character in the buffer that we need to process next + ch = this.m_current.Lastchar; + continue; + } + } + else + { + // must be just part of the CDATA block, so proceed. + this.m_sb.Append('<'); + this.m_sb.Append(ch); + ws = false; + } + } + else + { + if (!this.m_current.IsWhitespace && ws) + ws = false; + this.m_sb.Append(ch); + } + + ch = this.m_current.ReadChar(); + } + + // NOTE (steveb): check if we reached EOF, which means it's over + if(ch == Entity.EOF) { + this.m_state = State.Eof; + return false; + } + + string value = this.m_sb.ToString(); + + // NOTE (steveb): replace any nested CDATA sections endings + value = value.Replace("", string.Empty); + value = value.Replace("/**/", string.Empty); + + Push(null, XmlNodeType.CDATA, value); + if (this.m_partial == '\0') + this.m_partial = ' ';// force it to pop this CDATA next time in. + + return true; + } + + private void ExpandEntity(StringBuilder sb, char terminator) + { + char ch = this.m_current.ReadChar(); + if (ch == '#') + { + string charent = this.m_current.ExpandCharEntity(); + sb.Append(charent); + ch = this.m_current.Lastchar; + } + else + { + this.m_name.Length = 0; + while (ch != Entity.EOF && + (char.IsLetter(ch) || ch == '_' || ch == '-') || ((this.m_name.Length > 0) && char.IsDigit(ch))) + { + this.m_name.Append(ch); + ch = this.m_current.ReadChar(); + } + string name = this.m_name.ToString(); + + // TODO (steveb): don't lookup amp, gt, lt, quote + switch(name) { + case "amp": + sb.Append("&"); + if(ch != terminator && ch != '&' && ch != Entity.EOF) + ch = this.m_current.ReadChar(); + return; + case "lt": + sb.Append("<"); + if(ch != terminator && ch != '&' && ch != Entity.EOF) + ch = this.m_current.ReadChar(); + return; + case "gt": + sb.Append(">"); + if(ch != terminator && ch != '&' && ch != Entity.EOF) + ch = this.m_current.ReadChar(); + return; + case "quot": + sb.Append("\""); + if(ch != terminator && ch != '&' && ch != Entity.EOF) + ch = this.m_current.ReadChar(); + return; + case "apos": + sb.Append("'"); + if(ch != terminator && ch != '&' && ch != Entity.EOF) + ch = this.m_current.ReadChar(); + return; + } + + if (this.m_dtd != null && !string.IsNullOrEmpty(name)) + { + Entity e = (Entity)this.m_dtd.FindEntity(name); + if (e != null) + { + if (e.IsInternal) + { + sb.Append(e.Literal); + if (ch != terminator && ch != '&' && ch != Entity.EOF) + ch = this.m_current.ReadChar(); + + return; + } + else + { + Entity ex = new Entity(name, e.PublicId, e.Uri, this.m_current.Proxy); + e.Open(this.m_current, new Uri(e.Uri)); + this.m_current = ex; + this.m_current.ReadChar(); + return; + } + } + else + { + Log("Undefined entity '{0}'", name); + } + } + // Entity is not defined, so just keep it in with the rest of the + // text. + sb.Append("&"); + sb.Append(name); + if(ch != terminator && ch != '&' && ch != Entity.EOF) + { + sb.Append(ch); + ch = this.m_current.ReadChar(); + } + } + } + + /// + /// Gets a value indicating whether the reader is positioned at the end of the stream. + /// + /// true if the reader is positioned at the end of the stream; otherwise, false. + public override bool EOF + { + get + { + return this.m_state == State.Eof; + } + } + +#if !PORTABLE + public override void Close() + { + ((IDisposable)this).Dispose(); + } +#endif + + /// + /// Changes the to Closed. + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + { + return; + } + + if (this.m_current != null) + { + this.m_current.Close(); + this.m_current = null; + } + + if (this.m_log != null) + { + this.m_log.Dispose(); + this.m_log = null; + } + } + + /// + /// Gets the state of the reader. + /// + /// One of the ReadState values. + public override ReadState ReadState + { + get + { + if (this.m_state == State.Initial) + return ReadState.Initial; + else if (this.m_state == State.Eof) + return ReadState.EndOfFile; + else + return ReadState.Interactive; + } + } + + /// + /// Reads the contents of an element or text node as a string. + /// + /// The contents of the element or an empty string. +#if PORTABLE + public override string ReadContentAsString() +#else + public override string ReadString() +#endif + { + if (this.m_node.NodeType == XmlNodeType.Element) + { + this.m_sb.Length = 0; + while (Read()) + { + switch (this.NodeType) + { + case XmlNodeType.CDATA: + case XmlNodeType.SignificantWhitespace: + case XmlNodeType.Whitespace: + case XmlNodeType.Text: + this.m_sb.Append(this.m_node.Value); + break; + default: + return this.m_sb.ToString(); + } + } + + return this.m_sb.ToString(); + } + + return this.m_node.Value; + } + + /// + /// Reads all the content, including markup, as a string. + /// + /// + /// All the XML content, including markup, in the current node. If the current node has no children, + /// an empty string is returned. If the current node is neither an element nor attribute, an empty + /// string is returned. + /// + public override string ReadInnerXml() + { + StringWriter sw = new StringWriter(CultureInfo.InvariantCulture); + XmlWriter xw = XmlWriter.Create(sw, new XmlWriterSettings { Indent = true, IndentChars = " "}); + switch (this.NodeType) + { + case XmlNodeType.Element: + Read(); + while (!this.EOF && this.NodeType != XmlNodeType.EndElement) + { + xw.WriteNode(this, true); + } + Read(); // consume the end tag + break; + case XmlNodeType.Attribute: + sw.Write(this.Value); + break; + default: + // return empty string according to XmlReader spec. + break; + } + +#if PORTABLE + xw.Dispose(); +#else + xw.Close(); +#endif + return sw.ToString(); + } + + /// + /// Reads the content, including markup, representing this node and all its children. + /// + /// + /// If the reader is positioned on an element or an attribute node, this method returns all the XML content, including markup, of the current node and all its children; otherwise, it returns an empty string. + /// + public override string ReadOuterXml() + { + StringWriter sw = new StringWriter(CultureInfo.InvariantCulture); + XmlWriter xw = XmlWriter.Create(sw, new XmlWriterSettings {Indent = true, IndentChars = " "}); xw.WriteNode(this, true); - } - Read(); // consume the end tag - break; - case XmlNodeType.Attribute: - sw.Write(this.Value); - break; - default: - // return empty string according to XmlReader spec. - break; - } - - xw.Dispose(); - - return sw.ToString(); - } - - /// - /// Reads the content, including markup, representing this node and all its children. - /// - /// - /// If the reader is positioned on an element or an attribute node, this method returns all the XML content, including markup, of the current node and all its children; otherwise, it returns an empty string. - /// - public override string ReadOuterXml() - { - StringWriter sw = new StringWriter(CultureInfo.InvariantCulture); - XmlWriter xw = XmlWriter.Create(sw, new XmlWriterSettings { Indent = true, IndentChars = " " }); - xw.WriteNode(this, true); - - xw.Dispose(); - - return sw.ToString(); - } - - /// - /// Gets the XmlNameTable associated with this implementation. - /// - /// The XmlNameTable enabling you to get the atomized version of a string within the node. - public override XmlNameTable NameTable - { - get - { - return null; - } - } - - /// - /// Resolves a namespace prefix in the current element's scope. - /// - /// The prefix whose namespace URI you want to resolve. To match the default namespace, pass an empty string. - /// The namespace URI to which the prefix maps or a null reference (Nothing in Visual Basic) if no matching prefix is found. - public override string LookupNamespace(string prefix) - { - return null; // there are no namespaces in SGML. - } - - /// - /// Resolves the entity reference for EntityReference nodes. - /// - /// SgmlReader does not resolve or return entities. - public override void ResolveEntity() - { - // We never return any entity reference nodes, so this should never be called. - throw new InvalidOperationException("Not on an entity reference."); - } - - /// - /// Parses the attribute value into one or more Text, EntityReference, or EndEntity nodes. - /// - /// - /// true if there are nodes to return. false if the reader is not positioned on an attribute node when the initial call is made or if all the - /// attribute values have been read. An empty attribute, such as, misc="", returns true with a single node with a value of string.Empty. - /// - public override bool ReadAttributeValue() - { - if (this.m_state == State.Attr) - { - this.m_state = State.AttrValue; - return true; - } - else if (this.m_state == State.AttrValue) - { - return false; - } - else - throw new InvalidOperationException("Not on an attribute."); - } - - private void Validate(Node node) - { - if (this.m_dtd != null) - { - ElementDecl e = this.m_dtd.FindElement(node.Name); - if (e != null) - { - node.DtdType = e; - if (e.ContentModel.DeclaredContent == DeclaredContent.EMPTY) - node.IsEmpty = true; - } - } - } - - private static void ValidateAttribute(Node node, Attribute a) - { - ElementDecl e = node.DtdType; - if (e != null) - { - AttDef ad = e.FindAttribute(a.Name); - if (ad != null) - { - a.DtdType = ad; - } - } - } - - private static bool ValidAttributeName(string name) - { - try - { - XmlConvert.VerifyNMTOKEN(name); - int index = name.IndexOf(':'); - if (index >= 0) - { - XmlConvert.VerifyNCName(name.Substring(index + 1)); +#if PORTABLE + xw.Dispose(); +#else + xw.Close(); +#endif + return sw.ToString(); } - return true; - } - catch (XmlException) - { - return false; - } - catch (ArgumentNullException) - { - // (steveb) this is probably a bug in XmlConvert.VerifyNCName when passing in an empty string - return false; - } - } - - private void ValidateContent(Node node) - { - if (node.NodeType == XmlNodeType.Element) - { - if (!VerifyName(node.Name)) + /// + /// Gets the XmlNameTable associated with this implementation. + /// + /// The XmlNameTable enabling you to get the atomized version of a string within the node. + public override XmlNameTable NameTable { - Pop(); - Push(null, XmlNodeType.Text, "<" + node.Name + ">"); - return; - } - } - - if (this.m_dtd != null) - { - // See if this element is allowed inside the current element. - // If it isn't, then auto-close elements until we find one - // that it is allowed to be in. - string name = node.Name.ToUpperInvariant(); // DTD is in upper case - int i = 0; - int top = this.m_stack.Count - 2; - if (node.DtdType != null) - { - // it is a known element, let's see if it's allowed in the - // current context. - for (i = top; i > 0; i--) - { - Node n = (Node)this.m_stack[i]; - if (n.IsEmpty) - continue; // we'll have to pop this one - ElementDecl f = n.DtdType; - if (f != null) + get { - if ((i == 2) && string.Equals(f.Name, "BODY", StringComparison.OrdinalIgnoreCase)) // NOTE (steveb): never close the BODY tag too early - break; - else if (string.Equals(f.Name, this.m_dtd.Name, StringComparison.OrdinalIgnoreCase)) - break; // can't pop the root element. - else if (f.CanContain(name, this.m_dtd)) - { - break; - } - else if (!f.EndTagOptional) - { - // If the end tag is not optional then we can't - // auto-close it. We'll just have to live with the - // junk we've found and move on. - break; - } + return null; + } + } + + /// + /// Resolves a namespace prefix in the current element's scope. + /// + /// The prefix whose namespace URI you want to resolve. To match the default namespace, pass an empty string. + /// The namespace URI to which the prefix maps or a null reference (Nothing in Visual Basic) if no matching prefix is found. + public override string LookupNamespace(string prefix) + { + return null; // there are no namespaces in SGML. + } + + /// + /// Resolves the entity reference for EntityReference nodes. + /// + /// SgmlReader does not resolve or return entities. + public override void ResolveEntity() + { + // We never return any entity reference nodes, so this should never be called. + throw new InvalidOperationException("Not on an entity reference."); + } + + /// + /// Parses the attribute value into one or more Text, EntityReference, or EndEntity nodes. + /// + /// + /// true if there are nodes to return. false if the reader is not positioned on an attribute node when the initial call is made or if all the + /// attribute values have been read. An empty attribute, such as, misc="", returns true with a single node with a value of string.Empty. + /// + public override bool ReadAttributeValue() + { + if (this.m_state == State.Attr) + { + this.m_state = State.AttrValue; + return true; + } + else if (this.m_state == State.AttrValue) + { + return false; } else + throw new InvalidOperationException("Not on an attribute."); + } + + private void Validate(Node node) + { + if (this.m_dtd != null) { - // Since we don't understand this tag anyway, - // we might as well allow this content! - break; + ElementDecl e = this.m_dtd.FindElement(node.Name); + if (e != null) + { + node.DtdType = e; + if (e.ContentModel.DeclaredContent == DeclaredContent.EMPTY) + node.IsEmpty = true; + } } - } } - if (i == 0) + private static void ValidateAttribute(Node node, Attribute a) { - // Tag was not found or is not allowed anywhere, ignore it and - // continue on. - return; + ElementDecl e = node.DtdType; + if (e != null) + { + AttDef ad = e.FindAttribute(a.Name); + if (ad != null) + { + a.DtdType = ad; + } + } } - else if (i < top) + + private static bool ValidAttributeName(string name) { - Node n = (Node)this.m_stack[top]; - if (i == top - 1 && string.Equals(name, n.Name, StringComparison.OrdinalIgnoreCase)) - { - // e.g. p not allowed inside p, not an interesting error. - } - else - { + try + { + XmlConvert.VerifyNMTOKEN(name); + int index = name.IndexOf(':'); + if (index >= 0) + { + XmlConvert.VerifyNCName(name.Substring(index + 1)); + } - } - - this.m_state = State.AutoClose; - this.m_newnode = node; - Pop(); // save this new node until we pop the others - this.m_poptodepth = i + 1; + return true; + } + catch (XmlException) + { + return false; + } + catch (ArgumentNullException) + { + // (steveb) this is probably a bug in XmlConvert.VerifyNCName when passing in an empty string + return false; + } + } + + private void ValidateContent(Node node) + { + if (node.NodeType == XmlNodeType.Element) + { + if (!VerifyName(node.Name)) + { + Pop(); + Push(null, XmlNodeType.Text, "<" + node.Name + ">"); + return; + } + } + + if (this.m_dtd != null) + { + // See if this element is allowed inside the current element. + // If it isn't, then auto-close elements until we find one + // that it is allowed to be in. + string name = node.Name.ToUpperInvariant(); // DTD is in upper case + int i = 0; + int top = this.m_stack.Count - 2; + if (node.DtdType != null) { + // it is a known element, let's see if it's allowed in the + // current context. + for (i = top; i > 0; i--) + { + Node n = (Node)this.m_stack[i]; + if (n.IsEmpty) + continue; // we'll have to pop this one + ElementDecl f = n.DtdType; + if (f != null) + { + if ((i == 2) && string.Equals(f.Name, "BODY", StringComparison.OrdinalIgnoreCase)) // NOTE (steveb): never close the BODY tag too early + break; + else if (string.Equals(f.Name, this.m_dtd.Name, StringComparison.OrdinalIgnoreCase)) + break; // can't pop the root element. + else if (f.CanContain(name, this.m_dtd)) + { + break; + } + else if (!f.EndTagOptional) + { + // If the end tag is not optional then we can't + // auto-close it. We'll just have to live with the + // junk we've found and move on. + break; + } + } + else + { + // Since we don't understand this tag anyway, + // we might as well allow this content! + break; + } + } + } + + if (i == 0) + { + // Tag was not found or is not allowed anywhere, ignore it and + // continue on. + return; + } + else if (i < top) + { + Node n = (Node)this.m_stack[top]; + if (i == top - 1 && string.Equals(name, n.Name, StringComparison.OrdinalIgnoreCase)) + { + // e.g. p not allowed inside p, not an interesting error. + } + else + { +#if DEBUG + string closing = ""; + for (int k = top; k >= i+1; k--) { + if (closing != "") closing += ","; + Node n2 = (Node)this.m_stack[k]; + closing += "<" + n2.Name + ">"; + } + Log("Element '{0}' not allowed inside '{1}', closing {2}.", name, n.Name, closing); +#endif + } + + this.m_state = State.AutoClose; + this.m_newnode = node; + Pop(); // save this new node until we pop the others + this.m_poptodepth = i + 1; + } + } } - } } - } } \ No newline at end of file diff --git a/PortablePorts/SgmlReader/SgmlReader.csproj b/PortablePorts/SgmlReader/SgmlReader.csproj index 4193c72..2228500 100644 --- a/PortablePorts/SgmlReader/SgmlReader.csproj +++ b/PortablePorts/SgmlReader/SgmlReader.csproj @@ -11,7 +11,7 @@ SgmlReader SgmlReader v4.0 - Profile96 + Profile104 512 {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -28,13 +28,13 @@ pdbonly true bin\Release\ - TRACE + TRACE;PORTABLE prompt 4 - + diff --git a/PortablePorts/SgmlReader/htmllat1.ent b/PortablePorts/SgmlReader/htmllat1.ent new file mode 100644 index 0000000..c644486 --- /dev/null +++ b/PortablePorts/SgmlReader/htmllat1.ent @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file