context: // AElfred XML Parser. This version of the AElfred parser is
  // derived from the original Microstar distribution, with additional
  // bug fixes by Michael Kay, and selected enhancements and further
  // bug fixes from the version produced by David Brownell.
  // ported to .Net by Rasmus John Pedersen
  
  namespace AElfred.Net
  {
    using System;
    using System.Collections;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Text;
  
    using Sax.Net;
  
    /// <summary>
    ///   Parse XML documents and return parse events through call-backs.
    ///   Use the <c>SAXDriver</c> class as your entry point, as the
    ///   internal parser interfaces are subject to change.
    /// </summary>
    /// <seealso cref="SAXDriver" />
    internal sealed class XmlParser
    {
      // parse from buffer, avoiding slow per-character readCh()
      private const bool USE_CHEATS = true;
  
      // don't waste too much space in hashtables 
      private const int DEFAULT_ATTR_COUNT = 23;
  
  
      //////////////////////////////////////////////////////////////////////
      // Constructors.
      ////////////////////////////////////////////////////////////////////////
  
  
      /**
       * Construct a new parser with no associated handler.
       * @see #setHandler
       * @see #parse
       */
      // package private
      public XmlParser()
      {
        cleanupVariables();
      }
  
  
      /**
       * Set the handler that will receive parsing events.
       * @param handler The handler to receive callback events.
       * @see #parse
       */
      // package private
      public void setHandler(SAXDriver handler)
      {
        this._handler = handler;
      }
  
  
      /**
       * Parse an XML document from the character stream, byte stream, or URI
       * that you provide (in that order of preference).  Any URI that you
       * supply will become the base URI for resolving relative URI, and may
       * be used to acquire a reader or byte stream.
       *
       * <p>You may parse more than one document, but that must be done
       * sequentially.  Only one thread at a time may use this parser.
       *
       * @param systemId The URI of the document; should never be null,
       *	but may be so iff a reader <em>or</em> a stream is provided.
       * @param publicId The public identifier of the document, or null.
       * @param reader A character stream; must be null if stream isn't.
       * @param stream A byte input stream; must be null if reader isn't.
       * @param encoding The suggested encoding, or null if unknown.
       * @exception java.lang.Exception Basically SAXException or IOException
       */
      // package private 
      public void DoParse(string systemId, string publicId, TextReader reader, Stream stream, Encoding encoding)
      {
        if (this._handler == null) throw new InvalidOperationException("no callback handler");
  
        this._basePublicId = publicId;
        this._baseURI = systemId;
        this._baseReader = reader;
        this._baseInputStream = stream;
  
        initializeVariables();
  
        // predeclare the built-in entities here (replacement texts)
        // we don't need to intern(), since we're guaranteed literals
        // are always (globally) interned.
        setInternalEntity("amp", "&#38;");
        setInternalEntity("lt", "&#60;");
        setInternalEntity("gt", "&#62;");
        setInternalEntity("apos", "&#39;");
        setInternalEntity("quot", "&#34;");
  
        this._handler.StartDocument();
  
        pushURL("[document]", this._basePublicId, this._baseURI, this._baseReader, this._baseInputStream, encoding, false);
  
        try
        {
          parseDocument();
          this._handler.EndDocument();
        }
        finally
        {
          if (this._baseReader != null)
            try
            {
              this._baseReader.Close();
            }
            catch (IOException e)
            {
              /* ignore */
            }
          if (this._baseInputStream != null)
            try
            {
              this._baseInputStream.Close();
            }
            catch (IOException e)
            {
              /* ignore */
            }
          if (this._stream != null)
            try
            {
              this._stream.Close();
            }
            catch (IOException e)
            {
              /* ignore */
            }
          if (reader != null)
            try
            {
              reader.Close();
            }
            catch (IOException e)
            {
              /* ignore */
            }
          cleanupVariables();
        }
      }
  
  
      ////////////////////////////////////////////////////////////////////////
      // Constants.
      ////////////////////////////////////////////////////////////////////////
  
      //
      // Constants for element content type.
      //
  
      /**
       * Constant: an element has not been declared.
       * @see #getElementContentType
       */
  
      public const int CONTENT_UNDECLARED = 0;
  
      /**
       * Constant: the element has a content model of ANY.
       * @see #getElementContentType
       */
  
      public const int CONTENT_ANY = 1;
  
      /**
       * Constant: the element has declared content of EMPTY.
       * @see #getElementContentType
       */
  
      public const int CONTENT_EMPTY = 2;
  
      /**
       * Constant: the element has mixed content.
       * @see #getElementContentType
       */
  
      public const int CONTENT_MIXED = 3;
  
      /**
       * Constant: the element has element content.
       * @see #getElementContentType
       */
  
      public const int CONTENT_ELEMENTS = 4;
  
  
      //
      // Constants for the entity type.
      //
  
      /**
       * Constant: the entity has not been declared.
       * @see #getEntityType
       */
  
      public const int ENTITY_UNDECLARED = 0;
  
      /**
       * Constant: the entity is internal.
       * @see #getEntityType
       */
  
      public const int ENTITY_INTERNAL = 1;
  
      /**
       * Constant: the entity is external, non-parseable data.
       * @see #getEntityType
       */
  
      public const int ENTITY_NDATA = 2;
  
      /**
       * Constant: the entity is external XML data.
       * @see #getEntityType
       */
  
      public const int ENTITY_TEXT = 3;
  
  
      //
      // Constants for attribute type.
      //
  
      /**
       * Constant: the attribute has not been declared for this element type.
       * @see #getAttributeType
       */
  
      public const int ATTRIBUTE_UNDECLARED = 0;
  
      /**
       * Constant: the attribute value is a string value.
       * @see #getAttributeType
       */
  
      public const int ATTRIBUTE_CDATA = 1;
  
      /**
       * Constant: the attribute value is a unique identifier.
       * @see #getAttributeType
       */
  
      public const int ATTRIBUTE_ID = 2;
  
      /**
       * Constant: the attribute value is a reference to a unique identifier.
       * @see #getAttributeType
       */
  
      public const int ATTRIBUTE_IDREF = 3;
  
      /**
       * Constant: the attribute value is a list of ID references.
       * @see #getAttributeType
       */
  
      public const int ATTRIBUTE_IDREFS = 4;
  
      /**
       * Constant: the attribute value is the name of an entity.
       * @see #getAttributeType
       */
  
      public const int ATTRIBUTE_ENTITY = 5;
  
      /**
       * Constant: the attribute value is a list of entity names.
       * @see #getAttributeType
       */
  
      public const int ATTRIBUTE_ENTITIES = 6;
  
      /**
       * Constant: the attribute value is a name token.
       * @see #getAttributeType
       */
  
      public const int ATTRIBUTE_NMTOKEN = 7;
  
      /**
       * Constant: the attribute value is a list of name tokens.
       * @see #getAttributeType
       */
  
      public const int ATTRIBUTE_NMTOKENS = 8;
  
      /**
       * Constant: the attribute value is a token from an enumeration.
       * @see #getAttributeType
       */
  
      public const int ATTRIBUTE_ENUMERATED = 9;
  
      /**
       * Constant: the attribute is the name of a notation.
       * @see #getAttributeType
       */
  
      public const int ATTRIBUTE_NOTATION = 10;
  
  
      //
      // When the class is loaded, populate the hash table of
      // attribute types.
      //
  
      /**
       * Hash table of attribute types.
       */
  
      private static Hashtable attributeTypeHash;
  
      static XmlParser()
      {
        attributeTypeHash = new Hashtable(13);
        attributeTypeHash.Add("CDATA", ATTRIBUTE_CDATA);
        attributeTypeHash.Add("ID", ATTRIBUTE_ID);
        attributeTypeHash.Add("IDREF", ATTRIBUTE_IDREF);
        attributeTypeHash.Add("IDREFS", ATTRIBUTE_IDREFS);
        attributeTypeHash.Add("ENTITY", ATTRIBUTE_ENTITY);
        attributeTypeHash.Add("ENTITIES", ATTRIBUTE_ENTITIES);
        attributeTypeHash.Add("NMTOKEN", ATTRIBUTE_NMTOKEN);
        attributeTypeHash.Add("NMTOKENS", ATTRIBUTE_NMTOKENS);
        attributeTypeHash.Add("NOTATION", ATTRIBUTE_NOTATION);
      }
  
  
      //
      // Constants for supported encodings.  "external" is just a flag.
      //
      private const int ENCODING_EXTERNAL = 0;
  
      private const int ENCODING_UTF_8 = 1;
  
      private const int ENCODING_ISO_8859_1 = 2;
  
      private const int ENCODING_UCS_2_12 = 3;
  
      private const int ENCODING_UCS_2_21 = 4;
  
      private const int ENCODING_UCS_4_1234 = 5;
  
      private const int ENCODING_UCS_4_4321 = 6;
  
      private const int ENCODING_UCS_4_2143 = 7;
  
      private const int ENCODING_UCS_4_3412 = 8;
  
      private const int ENCODING_ASCII = 9;
  
  
      //
      // Constants for attribute default value.
      //
  
      /**
       * Constant: the attribute is not declared.
       * @see #getAttributeDefaultValueType
       */
  
      public const int ATTRIBUTE_DEFAULT_UNDECLARED = 30;
  
      /**
       * Constant: the attribute has a literal default value specified.
       * @see #getAttributeDefaultValueType
       * @see #getAttributeDefaultValue
       */
  
      public const int ATTRIBUTE_DEFAULT_SPECIFIED = 31;
  
      /**
       * Constant: the attribute was declared #IMPLIED.
       * @see #getAttributeDefaultValueType
       */
  
      public const int ATTRIBUTE_DEFAULT_IMPLIED = 32;
  
      /**
       * Constant: the attribute was declared #REQUIRED.
       * @see #getAttributeDefaultValueType
       */
  
      public const int ATTRIBUTE_DEFAULT_REQUIRED = 33;
  
      /**
       * Constant: the attribute was declared #FIXED.
       * @see #getAttributeDefaultValueType
       * @see #getAttributeDefaultValue
       */
  
      public const int ATTRIBUTE_DEFAULT_FIXED = 34;
  
  
      //
      // Constants for input.
      //
      private const int INPUT_NONE = 0;
  
      private const int INPUT_INTERNAL = 1;
  
      private const int INPUT_STREAM = 3;
  
      private const int INPUT_BUFFER = 4;
  
      private const int INPUT_READER = 5;
  
  
      //
      // Flags for reading literals.
      //
      // expand general entity refs (attribute values in dtd and content)
      private const int LIT_ENTITY_REF = 2;
  
      // normalize this value (space chars) (attributes, public ids)
      private const int LIT_NORMALIZE = 4;
  
      // literal is an attribute value 
      private const int LIT_ATTRIBUTE = 8;
  
      // don't expand parameter entities
      private const int LIT_DISABLE_PE = 16;
  
      // don't expand [or parse] character refs
      private const int LIT_DISABLE_CREF = 32;
  
      // don't parse general entity refs
      private const int LIT_DISABLE_EREF = 64;
  
      // don't expand general entities, but make sure we _could_
      private const int LIT_ENTITY_CHECK = 128;
  
      // literal is a public ID value 
      private const int LIT_PUBID = 256;
  
      //
      // Flags affecting PE handling in DTDs (if expandPE is true).
      // PEs expand with space padding, except inside literals.
      //
      private const int CONTEXT_NORMAL = 0;
  
      private const int CONTEXT_LITERAL = 1;
  
  
      //////////////////////////////////////////////////////////////////////
      // Error reporting.
      //////////////////////////////////////////////////////////////////////
  
  
      /**
       * Report an error.
       * @param message The error message.
       * @param textFound The text that caused the error (or null).
       * @see SAXDriver#error
       * @see #line
       */
  
      private void error(string message, string textFound, string textExpected)
      {
        if (textFound != null)
        {
          message = message + " (found \"" + textFound + "\")";
        }
        if (textExpected != null)
        {
          message = message + " (expected \"" + textExpected + "\")";
        }
        string uri = null;
  
        if (this._externalEntity != null)
        {
          uri = this._externalEntity.RequestUri.ToString();
        }
        this._handler.Error(message, uri, this._line, this._column);
  
        // "can't happen"
        throw new SAXException(message);
      }
  
  
      /**
       * Report a serious error.
       * @param message The error message.
       * @param textFound The text that caused the error (or null).
       */
  
      private void error(string message, char textFound, string textExpected)
      {
        error(message, textFound.ToString(CultureInfo.CurrentCulture), textExpected);
      }
  
      /** Report typical case fatal errors. */
  
      private void error(string message)
      {
        error(message, null, null);
      }
  
  
      //////////////////////////////////////////////////////////////////////
      // Major syntactic productions.
      //////////////////////////////////////////////////////////////////////
  
  
      /**
       * Parse an XML document.
       * <pre>
       * [1] document ::= prolog element Misc*
       * </pre>
       * <p>This is the top-level parsing function for a single XML
       * document.  As a minimum, a well-formed document must have
       * a document element, and a valid document must have a prolog
       * (one with doctype) as well.
       */
  
      private void parseDocument()
      {
        try
        {
          // added by MHK
          parseProlog();
          require('<', "document prolog");
          parseElement();
        }
        catch (EndOfStreamException ee)
        {
          // added by MHK
          error("premature end of file", "[EOF]", null);
        }
  
        try
        {
          parseMisc(); //skip all white, PIs, and comments
          char c = readCh(); //if this doesn't throw an exception...
          error("unexpected characters after document end", c, null);
        }
        catch (EndOfStreamException e)
        {
          return;
        }
      }
  
  
      /**
       * Skip a comment.
       * <pre>
       * [15] Comment ::= '&lt;!--' ((Char - '-') | ('-' (Char - '-')))* "-->"
       * </pre>
       * <p> (The <code>&lt;!--</code> has already been read.)
       */
  
      private void parseComment()
      {
        char c;
        bool saved = this._expandPE;
  
        this._expandPE = false;
        parseUntil("--");
        require('>', "-- in comment");
        this._expandPE = saved;
        this._handler.Comment(this._dataBuffer, 0, this._dataBufferPos);
        this._dataBufferPos = 0;
      }
  
  
      /**
       * Parse a processing instruction and do a call-back.
       * <pre>
       * [16] PI ::= '&lt;?' PITarget
       *		(S (Char* - (Char* '?&gt;' Char*)))?
       *		'?&gt;'
       * [17] PITarget ::= Name - ( ('X'|'x') ('M'|m') ('L'|l') )
       * </pre>
       * <p> (The <code>&lt;?</code> has already been read.)
       */
  
      private void parsePI()
      {
        string name;
        bool saved = this._expandPE;
  
        this._expandPE = false;
        name = readNmtoken(true);
        if ("xml".Equals(name, StringComparison.InvariantCultureIgnoreCase)) error("Illegal processing instruction target", name, null);
        if (!tryRead("?>"))
        {
          requireWhitespace();
          parseUntil("?>");
        }
        this._expandPE = saved;
        this._handler.ProcessingInstruction(name, dataBufferToString());
      }
  
  
      /**
       * Parse a CDATA section.
       * <pre>
       * [18] CDSect ::= CDStart CData CDEnd
       * [19] CDStart ::= '&lt;![CDATA['
       * [20] CData ::= (Char* - (Char* ']]&gt;' Char*))
       * [21] CDEnd ::= ']]&gt;'
       * </pre>
       * <p> (The '&lt;![CDATA[' has already been read.)
       */
  
      private void parseCDSect()
      {
        parseUntil("]]>");
        dataBufferFlush();
      }
  
  
      /**
       * Parse the prolog of an XML document.
       * <pre>
       * [22] prolog ::= XMLDecl? Misc* (Doctypedecl Misc*)?
       * </pre>
       * <p>There are a couple of tricks here.  First, it is necessary to
       * declare the XML default attributes after the DTD (if present)
       * has been read. [??]  Second, it is not possible to expand general
       * references in attribute value literals until after the entire
       * DTD (if present) has been parsed.
       * <p>We do not look for the XML declaration here, because it was
       * handled by pushURL ().
       * @see pushURL
       */
  
      private void parseProlog()
      {
        parseMisc();
  
        if (tryRead("<!DOCTYPE"))
        {
          parseDoctypedecl();
          parseMisc();
        }
      }
  
  
      /**
       * Parse the XML declaration.
       * <pre>
       * [23] XMLDecl ::= '&lt;?xml' VersionInfo EncodingDecl? SDDecl? S? '?&gt;'
       * [24] VersionInfo ::= S 'version' Eq
       *		("'" VersionNum "'" | '"' VersionNum '"' )
       * [26] VersionNum ::= ([a-zA-Z0-9_.:] | '-')*
       * [32] SDDecl ::= S 'standalone' Eq
       *		( "'"" ('yes' | 'no') "'"" | '"' ("yes" | "no") '"' )
       * [80] EncodingDecl ::= S 'encoding' Eq
       *		( "'" EncName "'" | "'" EncName "'" )
       * [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*
       * </pre>
       * <p> (The <code>&lt;?xml</code> and whitespace have already been read.)
       * @return the encoding in the declaration, uppercased; or null
       * @see #parseTextDecl
       * @see #setupDecoding
       */
  
      private string parseXMLDecl(bool ignoreEncoding)
      {
        string version;
        string encodingName = null;
        string standalone = null;
        int flags = LIT_DISABLE_CREF | LIT_DISABLE_PE | LIT_DISABLE_EREF;
  
        // Read the version.
        require("version", "XML declaration");
        parseEq();
        version = readLiteral(flags);
        if (!version.Equals("1.0"))
        {
          error("unsupported XML version", version, "1.0");
        }
  
        // Try reading an encoding declaration.
        bool white = tryWhitespace();
        if (tryRead("encoding"))
        {
          if (!white) error("whitespace required before 'encoding='");
          parseEq();
          encodingName = readLiteral(flags);
          if (!ignoreEncoding) setupDecoding(encodingName);
        }
  
        // Try reading a standalone declaration
  
gt:       if (encodingName != null) white = tryWhitespace();