SaxExpat.cs :  » XML-Parsers » SAX.NET » Kds » Xml » Expat » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » XML Parsers » SAX.NET 
SAX.NET » Kds » Xml » Expat » SaxExpat.cs
/*
 * This software is licensed according to the "Modified BSD License",
 * where the following substitutions are made in the license template:
 * <OWNER> = Karl Waclawek
 * <ORGANIZATION> = Karl Waclawek
 * <YEAR> = 2004, 2005, 2006
 * It can be obtained from http://opensource.org/licenses/bsd-license.html.
 */

using System;
using System.Text;
using System.IO;
using System.Net;
using System.Resources;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Org.System.Xml.Sax;
using Org.System.Xml.Sax.Helpers;
using Kds.Xml.Sax;
using Kds.Xml.Sax.Helpers;
using Kds.Text;

namespace Kds.Xml.Expat{
  using Helpers = Org.System.Xml.Sax.Helpers;
  using SaxConsts = Org.System.Xml.Sax.Constants;
  using SaxRsId = Org.System.Xml.Sax.RsId;
  using KdsConsts = Kds.Xml.Sax.Constants;
  using KdsRsId = Kds.Xml.Sax.RsId;
  using SaxRes = Org.System.Xml.Sax.Resources;

  /**<summary>Identifies localized string constants.</summary> */
  public enum RsId
  {
    CannotGetInputContext,
    NoPositionForTextReader,
    UnexpectedParserResult,
    UnparsedEntityDeclError,
    ExternalEntityDeclError,
    NotationDeclError,
    InitParseError,
    StreamReaderInputSourceOnly,
    NamespacePrefixesDepends,
    AccessingBaseUri
  }

  /**<summary>Defines constants for the <see cref="Kds.Xml.Expat"/> namespace.</summary> */
  public class Constants
  {
    private static ResourceManager rm;

    private Constants(){}

    /// <summary>Character constant.</summary>
    public const char
      NullChar = (char)0,
      Colon = ':',
      Equal = '=';

    public const string
      ForeignDtdId = "<foreign_dtd>";  // is an invalid URI

    public const int
      UriTimeout = 6000;  // WebRequest timeout

    /// <summary>Returns localized string constants.</summary>
    public static string GetString(RsId id)
    {
      string name = Enum.GetName(typeof(RsId), id);
      return rm.GetString(name);
    }

    static Constants()
    {
      rm = new ResourceManager("Kds.Xml.Expat.SaxExpat", Assembly.GetExecutingAssembly());
    }
  }

  /**<summary>Expat specific <see cref="IAttributes"/> implementation.</summary> */
  internal unsafe class ExpatAttributes: IAttributes
  {
    private StringTable strTable;
    private AttributeDecls attDecls;
    private string elmName;
    private char** atts;
    private int specAttCount;
    private int totalAttCount;

    private void CheckIndex(int index)
    {
      if ((index < 0) || (index >= totalAttCount)) {
        string msg = SaxRes.GetString(SaxRsId.AttIndexOutOfBounds);
        throw new IndexOutOfRangeException(msg);
      }
    }

    private void NotFoundError(string qName)
    {
      string msg = SaxRes.GetString(SaxRsId.AttributeNotFound);
      throw new ArgumentException(String.Format(msg, qName), "qName");
    }

    private void NotFoundError(string uri, string localName)
    {
      string msg;
      if (uri == null || uri.Length == 0) {
        msg = SaxRes.GetString(SaxRsId.AttributeNotFound);
        msg = String.Format(msg, localName);
      }
      else {
        msg = SaxRes.GetString(SaxRsId.AttributeNotFoundNS);
        msg = String.Format(msg, uri, localName);
      }
      throw new ArgumentException(msg, "{uri}localName");
    }

    public ExpatAttributes(StringTable strTable)
    {
      this.strTable = strTable;
      attDecls = new AttributeDecls();
      elmName = null;
      atts = null;
      specAttCount = 0;
      totalAttCount = 0;
    }

    /* IAttributes */

    public int Length
    {
      get { return totalAttCount; }
    }

    public string GetUri(int index)
    {
      CheckIndex(index);
      string uri = ExpatUtils.ParseNameUri(atts[index << 1], strTable);
      return uri;
    }

    public string GetLocalName(int index)
    {
      CheckIndex(index);
      string locName = ExpatUtils.ParseNameLocal(atts[index << 1], strTable);
      return locName;
    }

    public string GetQName(int index)
    {
      CheckIndex(index);
      string qName = ExpatUtils.ParseNameQual(atts[index << 1], strTable);
      return qName;
    }

    /// <overloads>
    ///   <summary>
    ///     Returns declared type of attribute, or 'UNDECLARED' if no
    ///     declaration was read.
    ///   </summary>
    ///   <remarks>
    ///     Enumerated attributes are reported as type 'ENUMERATION'
    ///     according to the InfoSet spec, instead of 'NMTOKEN' as
    ///     required by the Java SAX2 specs.
    ///   </remarks>
    /// </overloads>
    public string GetType(int index)
    {
      string qName = GetQName(index);
      /* attDecls.GetEntry() is based on matching QNames, i.e. the declarations in
       * the DTD must have the same prefixes and local names as the element and
       * attribute names encountered in the document, since DTDs are not
       * namespace aware. Matching Uri + local name will not work.
       */
      AttDeclEntry entry = attDecls.GetEntry(elmName, qName);
      if (entry.Exists) {
        string attType = attDecls.GetAttType(entry);
        // check for enumerated type or notation - must have left parenthesis
        int parIndx = attType.IndexOf(KdsConsts.LeftPar);
        if (parIndx == -1)  // not found - regular type
          return attType;
        else if (parIndx == 0)  // first position: we have an enumerated type
          // SAX spec says: report as NMTOKEN
          // return Constants.xmlNmToken;
          // XML Infoset spec says: report as ENUMERATION
          return KdsConsts.XmlEnumeration;
        else  // left parenthesis is not first character: we have a notation
          return KdsConsts.XmlNotation;
      }
      else
        return KdsConsts.XmlUndeclared; // default
    }

    public string GetValue(int index)
    {
      CheckIndex(index);
      //kw should we intern the attribute value in strTable?
      char* valPtr = atts[(index << 1) + 1];
      if (valPtr == null)
        return null;
      int valLen = StringUtils.StrLen(valPtr);
      string value = new string(valPtr, 0, valLen);
      return value;
    }

    public int GetIndex(string uri, string localName)
    {
      int indx = totalAttCount - 1;
      string uriNm, locNm;
      while (indx >= 0) {
        ExpatUtils.ParseNameUriLocal(
          atts[indx << 1], strTable, out uriNm, out locNm);
        if (locNm == localName && uriNm == uri)
          break;
        indx--;
      }
      return indx;
    }

    public int GetIndex(string qName)
    {
      int indx = totalAttCount - 1;
      string qNm;
      while (indx >= 0) {
        qNm = ExpatUtils.ParseNameQual(atts[indx << 1], strTable);
        if (qNm == qName)
          break;
        indx--;
      }
      return indx;
    }

    public string GetType(string uri, string localName)
    {
      int indx = GetIndex(uri, localName);
      if (indx < 0)
        NotFoundError(uri, localName);
      return GetType(indx);
    }

    public string GetType(string qName)
    {
      int indx = GetIndex(qName);
      if (indx < 0)
        NotFoundError(qName);
      return GetType(indx);
    }

    public string GetValue(string uri, string localName)
    {
      int indx = GetIndex(uri, localName);
      if (indx < 0)
        NotFoundError(uri, localName);
      return GetValue(indx);
    }

    public string GetValue(string qName)
    {
      int indx = GetIndex(qName);
      if (indx < 0)
        NotFoundError(qName);
      return GetValue(indx);
    }

    public bool IsSpecified(int index)
    {
      CheckIndex(index);
      return (index < specAttCount);
    }

    public bool IsSpecified(string qName)
    {
      int indx = GetIndex(qName);
      if (indx < 0)
        NotFoundError(qName);
      return (indx < specAttCount);
    }

    public bool IsSpecified(string uri, string localName)
    {
      int indx = GetIndex(uri, localName);
      if (indx < 0)
        NotFoundError(uri, localName);
      return (indx < specAttCount);
    }

    /* end of IAttributes */

    public void GetName(int index,
                        out string uri,
                        out string localName,
                        out string qName)
    {
      CheckIndex(index);
      ExpatUtils.ParseNameSax(
              atts[index << 1], strTable, out uri, out localName, out qName);
    }


    // arguments only valid for duration of startElementHandler call-back
    internal void Initialize(string elmName, char** atts, int specAttCount)
    {
      this.elmName = elmName;
      this.atts = atts;
      this.specAttCount = specAttCount >> 1;
      int indx = specAttCount;
      while (atts[indx] != null) indx++;
      this.totalAttCount = this.specAttCount + ((indx - specAttCount) >> 1);
    }

    internal AttributeDecls AttDecls
    {
      get { return attDecls; }
    }
  }

  /**<summary>Expat specific subclass of <see cref="InputSource&lt;Stream>"/>.</summary>
   * <remarks>Used to differentiate between internally created and
   * and application provided input sources.</remarks>
   */
  internal class ExpatStreamSource: InputSource<Stream>
  {
    public ExpatStreamSource(Stream byteStream): base(byteStream) { }
  }

  /**<summary>Struct for internal use in <see cref="ExpatReader"/>. Groups
   * all fields that are used specifically during the parsing process.</summary>
   */
  internal unsafe struct DocParseData
  {
    private ExpatParseError error;
    private ExpatLocator locator;
    private StringBuilder contentBuilder;
    private char[] charBuffer;

    public bool Started;
    public int Standalone;
    public bool HasDoctypeDecl;
    public InputSource ForeignDtdSource;
    public XMLContent* ContentModel;

    public DocParseData(SaxExpatParser parser)
    {
      error = new ExpatParseError();
      locator = new ExpatLocator(parser);
      contentBuilder = new StringBuilder();
      charBuffer = new char[16];
      Started = false;
      Standalone = int.MaxValue;
      HasDoctypeDecl = false;
      ForeignDtdSource = null;
      ContentModel = null;
    }

    public void Reset()
    {
      Started = false;
      Standalone = int.MaxValue;
      ContentBuilder.Length = 0;
      HasDoctypeDecl = false;
      ForeignDtdSource = null;
      ContentModel = null;
    }

    public ExpatParseError Error(string message)
    {
      error.Init(message, Locator);
      return error;
    }

    public ExpatParseError Error(string message, int code)
    {
      error.Init(message, Locator, code);
      return error;
    }

    public ExpatParseError Error(string message, Exception e)
    {
      error.Init(message, Locator, e);
      return error;
    }

    public ExpatLocator Locator
    {
      get { return locator; }
    }

    public StringBuilder ContentBuilder
    {
      get { return contentBuilder; }
    }

    public char[] GetCharBuffer(int len)
    {
      if (charBuffer.Length < len)
        charBuffer = new char[len];
      return charBuffer;
    }
  }

  /**<summary>Expat specific <see cref="ParseError"/> implementation.</summary> */
  public class ExpatParseError: ParseError
  {
    private string message;
    private ExpatLocator locator;
    private int errorCode;
    private Exception baseException;

    private string ErrorUri(int expatCode)
    {
      return null;  // TO DO : map expat codes to exception uris
    }

    public void Init(string message, ExpatLocator locator)
    {
      this.message = message;
      this.locator = locator;
      this.errorCode = 0;
      this.baseException = null;
    }

    public void Init(string message, ExpatLocator locator, int errorCode)
    {
      this.message = message;
      this.locator = locator;
      this.errorCode = errorCode;
      this.baseException = null;
    }

    public void Init(string message, ExpatLocator locator, Exception e)
    {
      this.message = message;
      this.locator = locator;
      this.errorCode = 0;
      this.baseException = e;
    }

    public override string Message
    {
      get { return message; }
    }

    public override string ErrorId
    {
      get { return ErrorUri(errorCode); }
    }

     public override string PublicId
     {
       get { return locator.PublicId; }
     }

    public override string SystemId
    {
       get { return locator.SystemId; }
     }

    public override long LineNumber
    {
       get { return locator.LineNumber; }
     }

    public override long ColumnNumber
    {
       get { return locator.ColumnNumber; }
     }

     public override Exception BaseException
     {
       get { return baseException; }
     }
  }

  /**<summary>Expat specific <see cref="ILocator"/> implementation.</summary>
   * <remarks>The Expat specific <see cref="ParsePosition"/> method
   * is accessible when casting to the actual implementation class.</remarks>
   */
  public class ExpatLocator: ILocator
  {
    internal SaxExpatParser parser;

    internal ExpatLocator(SaxExpatParser parser)
    {
      Debug.Assert(parser != null);
      this.parser = parser;
    }

    /// <summary>Gives access to the parse position in the current input stream.</summary>
    /// <remarks>Since the current input stream changes when an external
    /// entity is parsed, one cannot access the parent document's parse
    /// position (or column/line number) unless one saves it in the
    /// <see cref="ILexicalHandler.StartEntity"/> callback</remarks>
    public unsafe long ParsePosition
    {
      get {
        SaxEntityContext entSC = parser.EntityContext;
        if (entSC.Reader != null) {
          string msg = Constants.GetString(RsId.NoPositionForTextReader);
          throw new SaxException(msg);
        }
        int offset = 0;
        int bufSize = 0;
        if (parser.GetInputContext(ref offset, ref bufSize) == null) {
          string msg = Constants.GetString(RsId.CannotGetInputContext);
          throw new SaxException(msg);
        }
        else  // InStream must be != null
          return entSC.Stream.Position - bufSize + offset;
      }
    }

    /* ILocator */

    public string PublicId
    {
      get { return parser.EntityContext.PublicId; }
    }

    // originates from InputSource.SystemId - is an absolute URI
    public string SystemId
    {
      get { return parser.EntityContext.SystemId; }
    }

    public unsafe long LineNumber
    {
      get { return parser.CurrentLineNumber; }
    }

    public unsafe long ColumnNumber
    {
      get { return parser.CurrentColumnNumber; }
    }

    public string XmlVersion
    {
      get { return parser.EntityContext.Version; }
    }

    public string Encoding
    {
      get { return parser.EntityContext.Encoding; }
    }

    public ParsedEntityType EntityType
    {
      get {
        SaxEntityContext entSC = parser.EntityContext;
        if (entSC.Parent == null) {
          switch (parser.UserData.parseData.Standalone) {
            case -1: return ParsedEntityType.Document;
            case 0: return ParsedEntityType.NotStandalone;
            case 1: return ParsedEntityType.Standalone;
            default: return ParsedEntityType.Unknown;
          }
        }
        else if (entSC.IsParameterEntity)
          return ParsedEntityType.Parameter;
        else
          return ParsedEntityType.General;
      }
    }
  }

  /**<summary>Wrapper class that exposes the (unmanaged) Expat XML parser
   * through the <see cref="IXmlReader"/> interface.</summary>
   * <remarks><list type="bullet">
   * <item>Works only with Expat 1.95.7 and later. To enable the features
   *   of a later version, specific conditional compile symbols must be defined.
   *   Currently the symbols EXPAT_1_95_8_UP and EXPAT_2_0_UP are recognized.
   *   EXPAT_1_95_8_UP enables the suspend/resume functionality introduced with
   *   Expat 1.95.8. EXPAT_2_0_UP enables 64-bit values for line- and column numbers.
   *   This requires that Expat 2.0 is compiled with XML_LARGE_SIZE defined.
   * </item>
   * <item>By casting the usual <see cref="IXmlReader"/> interface reference to
   *   <c>ExpatReader</c> one can gain access to some Expat specific functionality,
   *   and also to the currently active Expat parser instance, so that the
   *   functions exported by the Dll can be called directly (see Expat.cs).</item>
   * <item>SAX Features and Properties: <c>ExpatReader</c> also exposes Features
   *   and Properties directly as public properties for more efficient access.
   *   </item>
   * <item>Input sources: <c>ExpatReader</c> can currently only use <see cref="InputSource"/>,
   *   <see cref="InputSource&lt;Stream>"/> and <see cref="InputSource&lt;TextReader>"/> instances.</item>
   * <item>No Validation: <c>ExpatReader</c> processes and reports all DTD declarations
   *   but does not validate against them. However, attribute values are defaulted
   *   and entity references are replaced.</item>
   * <item><see cref="IContentHandler.IgnorableWhitespace"/> will not be called
   *   because the parser is non-validating. Such white space will be reported as
   *   character data.</item>
   * <item>The <see cref="IXmlReader.Suspend"/> function will not work while an
   *   external parameter entity is being parsed. It will silently fail, that is,
   *   one should check the <see cref="IXmlReader.Status"/> property immediately
   *   afterwards. Other events where it will fail silently are 
   *   <see cref="IContentHandler.EndDocument"/> and <see cref="ILexicalHandler.EndEntity"/>,
   *   even if correctly called when <see cref="IXmlReader.Status"/> =
   *   <see cref="XmlReaderStatus.Parsing"/>.</item>
   * <item>"http://xml.org/sax/features/namespace-prefixes" feature:
   *   When this feature is turned on, xmlns attributes (namespace declarations)
   *   are to be reported like regular attributes, in addition to being reported
   *   through the <see cref="IContentHandler.StartPrefixMapping"/> or
   *   <see cref="IContentHandler.EndPrefixMapping"/> events. However, the underlying
   *   Expat parser never reports xmlns attributes when namespace processing is turned
   *   on, so this feature will have the value <c>false</c> in this case. Conversely,
   *   it will assume the value <c>true</c> when namespace processing is turned off.
   *   This is automatically enforced when setting the namespaces feature.
   *   Trying to set the namespace-prefixes feature to a value conflicting with
   *   the namespaces feature will throw an exception.</item>
   * <item><see cref="IContentHandler.StartDocument"/> and
   *   <see cref="IContentHandler.EndDocument"/> call-backs: 
   *   Once <see cref="IContentHandler.StartDocument"/> was called, 
   *   <see cref="IContentHandler.EndDocument"/> will be called
   *   as well, even after a fatal error, or when an exception was raised,
   *   or when parsing was aborted by calling <see cref="IXmlReader.Abort"/>.
   *   If <see cref="IContentHandler.StartDocument"/> was not called, 
   *   <see cref="IContentHandler.EndDocument"/> will not be called either.
   *   This is independent of whether a content handler was assigned or not, so
   *   it should maybe read: "Once StartDocument() would have been called, ...".
   *   </item>
   * <item>This is how entities are reported:
   *   <list type="table">
   *     <listheader>We use these shorthand terms:</listheader>
   *     <item>ExternalGeneral = external-general-entities feature.</item>
   *     <item>LexicalParameter = lexical-handler/parameter-entities feature.</item>
   *     <item>SkipInternal = skip-internal-entities feature.</item>
   *   </list>
   *   <list type="table">
   *     <listheader>External General Entities:</listheader>
   *     <item>
   *       <term>ExternalGeneral = True.</term>
   *       <description><see cref="ILexicalHandler.StartEntity"/> and
   *       <see cref="ILexicalHandler.EndEntity"/> are called.</description>
   *     </item>
   *     <item>
   *       <term>ExternalGeneral = False.</term>
   *       <description><see cref="IContentHandler.SkippedEntity"/> is called.</description>
   *     </item>
   *   </list>
   *   <list type="table">
   *     <listheader>
   *       External Parameter Entities - LexicalParameter = True:
   *     </listheader>
   *     <item>
   *       <term>ExternalParameter = True.</term>
   *       <description><see cref="ILexicalHandler.StartEntity"/> and
   *       <see cref="ILexicalHandler.EndEntity"/> are called.</description>
   *     </item>
   *     <item>
   *       <term>ExternalParameter = False.</term>
   *       <description><see cref="IContentHandler.SkippedEntity"/> is called.</description>
   *     </item>
   *   </list>
   *   <list type="table">
   *     <listheader>
   *       External Parameter Entities - LexicalParameter = False:
   *     </listheader>
   *     <item>No calls, external parameter entities are silently ignored.</item>
   *   </list>
   *   <list type="table">
   *     <listheader>
   *       Internal Entities - LexicalParameter = True:
   *     </listheader>
   *     <item>
   *       <term>SkipInternal = True.</term>
   *       <description><see cref="IContentHandler.SkippedEntity"/> is called.</description>
   *     </item>
   *     <item>
   *       <term>SkipInternal = False.</term>
   *       <description>No calls, entities silently expanded.</description>
   *     </item>
   *   </list>
   *   <list type="table">
   *     <listheader>
   *       Internal Entities - LexicalParameter = False:
   *     </listheader>
   *     <item>
   *       Parameter entities are not parsed, i.e. silently ignored.
   *     </item>
   *   </list>
   * </item>
   * <item><see cref="ILexicalHandler"/>, internal entities:
   *   The <see cref="ILexicalHandler.StartEntity"/> and
   *   <see cref="ILexicalHandler.EndEntity"/> methods are only called for
   *   external entities, internal entities are not reported (except when
   *   skipped). There is the XML_GetCurrentByteCount() function in the Expat
   *   library which returns 0 if the current call-back is triggered from inside
   *   an internal entity - which could be useful.</item>
   * </list></remarks>
   */
  public class ExpatReader: IXmlReader, IDisposable
  {
    private StringTable strTable;

    SaxExpatParser parser;

    internal IEntityResolver entityResolver;
    private IContentHandler contentHandler;
    private ILexicalHandler lexicalHandler;
    private IDtdHandler dtdHandler;
    private IDeclHandler declHandler;
    private IErrorHandler errorHandler;

    private DefaultHandlerProperty defHandlerProp;

    private ExpatAttributes attributes;
    private ParameterEntityDecls paramEntityDecls;

    private bool
      namespaces,
      namespacePrefixes,
      externalGeneral,
      externalParameter,
      parameterEntities,
      standaloneError,
      parseUnlessStandalone,
      resolveDtdUris;

    private string foreignDoctypeName;

    // holds data used while parsing
    internal DocParseData parseData;

    #region Construction and Cleanup

    [CLSCompliant(false)]
    public ExpatReader(StringTable strTable)
    {
      StrTable = strTable;

      defHandlerProp = new DefaultHandlerProperty(null, OnDefHandlerChange);

      paramEntityDecls = new ParameterEntityDecls();
      attributes = new ExpatAttributes(strTable);

      namespaces = true;
      namespacePrefixes = false;
      parameterEntities = true;
      parseUnlessStandalone = false;
      resolveDtdUris = true;

      parser = new SaxExpatParser(null, namespaces, this);
      parseData = new DocParseData(parser);

      SetParamEntitiesForParsing(parameterEntities);
    }

    public ExpatReader(): this(new StringTable()) { }

    // call at end of each parse process
    protected virtual void Cleanup()
    {
      attributes.AttDecls.Clear();
      paramEntityDecls.Clear();
      parseData.Reset();
    }

    #endregion

    #region IDisposable

    // A derived class should not be able to override this method.
    public void Dispose()
    {
      Cleanup();
      parser.Dispose();
    }

    #endregion

    public bool Parsing
    {
      get { return parser.Parsing; }
    }

    #region Helpers

    private void OnDefHandlerChange(IProperty<IDefaultHandler> property, IDefaultHandler newValue)
    {
      SetDefHandlerForParsing(newValue != null, SkipInternal);
    }

    protected void CheckParsing(RsId whatId)
    {
      if (!Parsing) {
        string msg = KdsConsts.GetString(KdsRsId.IllegalWhenNotParsing);
        string what = Constants.GetString(whatId);
        throw new SaxException(String.Format(msg, what));
      }
    }

    private unsafe void SetLexicalHandlerForParsing(bool doSet)
    {
      if (doSet) {
        parser.CommentHandler = CommentHandlerImpl;
        parser.StartCdataSectionHandler = StartCdataSectionHandlerImpl;
        parser.EndCdataSectionHandler = EndCdataSectionHandlerImpl;
        /* always on - needed to store the DTD declaration
         * parser.StartDoctypeDeclHandler = StartDoctypeDeclHandlerImpl;
         * parser.EndDoctypeDeclHandler = EndDoctypeDeclHandlerImpl;
         */
      }
      else {
        parser.CommentHandler = null;
        parser.StartCdataSectionHandler = null;
        parser.EndCdataSectionHandler = null;
        /* parser.StartDoctypeDeclHandler = null;
         * parser.EndDoctypeDeclHandler = null;
         */
      }
    }

    private unsafe void SetDeclHandlerForParsing(bool doSet)
    {
      if (doSet) {
        parser.ElementDeclHandler = ElementDeclHandlerImpl;
        parser.AttlistDeclHandler = AttlistDeclHandlerImpl;
        /* always on
         * parser.EntityDeclHandler = EntityDeclHandlerImpl;
         */
      }
      else {
        parser.ElementDeclHandler = null;
        parser.AttlistDeclHandler = null;
        /* clear only if nobody else needs it
         * if (dtdHandler == null)
         *   parser.EntityDeclHandler = null;
         */
      }
    }

    private unsafe void SetDefHandlerForParsing(bool doSet, bool skipInternal)
    {
      if (doSet)
        parser.SetDefaultHandler(DefaultHandlerImpl, skipInternal);
      else
        parser.SetDefaultHandler(null, skipInternal);
    }

    private unsafe void SetContentHandlerForParsing(bool doSet)
    {
      if (doSet) {
        parser.StartElementHandler = StartElementHandlerImpl;
        parser.EndElementHandler = EndElementHandlerImpl;
        parser.CharacterDataHandler = CharacterDataHandlerImpl;
        parser.StartNamespaceDeclHandler = StartNamespaceDeclHandlerImpl;
        parser.EndNamespaceDeclHandler = EndNamespaceDeclHandlerImpl;
        parser.ProcessingInstructionHandler = ProcessingInstructionHandlerImpl;
        parser.SkippedEntityHandler = SkippedEntityHandlerImpl;
      }
      else {
        parser.StartElementHandler = null;
        parser.EndElementHandler = null;
        parser.CharacterDataHandler = null;
        parser.StartNamespaceDeclHandler = null;
        parser.EndNamespaceDeclHandler = null;
        parser.ProcessingInstructionHandler = null;
        parser.SkippedEntityHandler = null;
      }
    }

    private unsafe void SetDtdHandlerForParsing(bool doSet)
    {
      if (doSet) {
        parser.NotationDeclHandler = NotationDeclHandlerImpl;
        /* always on
         * parser.EntityDeclHandler = EntityDeclHandlerImpl;
         */
      }
      else {
        parser.NotationDeclHandler = null;
        /* clear only if no one else needs it
         * if (declHandlerProp.Value == null)
         *   parser.EntityDeclHandler = null;
         */
      }
    }

    private unsafe void SetNamespacesForParsing(bool doSet)
    {
      parser.Reset(parser.Encoding, doSet, doSet);
    }

    private unsafe void SetParamEntitiesForParsing(bool doSet)
    {
      XMLParamEntityParsing parEntParsing;
      if (doSet) {
        if (ParseUnlessStandalone)
          parEntParsing = XMLParamEntityParsing.UNLESS_STANDALONE;
        else
          parEntParsing = XMLParamEntityParsing.ALWAYS;
      }
      else
        parEntParsing = XMLParamEntityParsing.NEVER;

      if (!parser.SetParamEntityParsing(parEntParsing)) {
        string msg = SaxRes.GetString(SaxRsId.FeatureNotSupported);
        msg = String.Format(msg, SaxConsts.LexicalParameterFeature, doSet);
        CallErrorHandler(ErrorLevel.Error, msg);
      }
    }

    private unsafe void ProcessExpatError(XMLError errorCode)
    {
      ErrorLevel level;
      if (LibExpat.ErrorInSet(errorCode, XMLErrorSet.OPERATIONAL))
        level = ErrorLevel.Error;
      else  // well-formedness errors are always fatal
        level = ErrorLevel.Fatal;
      string errorMsg = LibExpat.XMLErrorString(errorCode);
      CallErrorHandler(level, errorMsg, (int)errorCode);
    }

    private unsafe ParseStatus StartParsing(InputSource source, bool isStreamSource)
    {
      Cleanup();
      // set up and initialize parsing environment for document entity
      bool sourceOwned = false;
      if (isStreamSource)
        sourceOwned = source is ExpatStreamSource;
      parser.Reset(source.Encoding, Namespaces, Namespaces);
      parser.EntityContext.Init(null, false, source, isStreamSource, sourceOwned);
      if (EntityResolver != null)
        parser.UseForeignDtd = true;

      /* set handlers that we always need */
      parser.XmlDeclHandler = XmlDeclHandlerImpl;
      // Need the next two handlers because storing parameter entity
      // declarations requires them, and because the ContentHandler or
      // LexicalHandler could be turned on at runtime.
      parser.EntityDeclHandler = EntityDeclHandlerImpl;
      parser.StartDoctypeDeclHandler = StartDoctypeDeclHandlerImpl;
      parser.EndDoctypeDeclHandler = EndDoctypeDeclHandlerImpl;
      parser.NotStandaloneHandler = NotStandaloneHandlerImpl;

      if (contentHandler != null) {
        contentHandler.SetDocumentLocator(parseData.Locator);
        parseData.Started = true;
        contentHandler.StartDocument();
      }
      else
        parseData.Started = true;

      return ContinueEntityParsing();
    }

    private void FinishParsing()
    {
      try {
        if (parseData.Started) {
          parseData.Started = false;
          if (contentHandler != null)
            contentHandler.EndDocument();
        }
      }
      finally {
        Cleanup();
      }
    }

    // will not touch non-URIs; requires sysId != null - not checked
    private string ResolveSystemId(string sysId, RsId msgId, string msgArg) 
    {
      string result = sysId;
      try {
        Uri sysIdUri;
        Uri baseUri = parser.EntityContext.BaseUri;
        if (baseUri == null)
          // we will get an exception if sysId is not an absolute URI
          sysIdUri = new Uri(sysId);
        else
          sysIdUri = new Uri(baseUri, sysId);
        result = sysIdUri.AbsoluteUri;
      }
      catch (UriFormatException) {
        // sysId is not an URI, so we don't touch it
      }
      catch (Exception e) {
        string msg = Constants.GetString(msgId);
        msg = String.Format(msg, msgArg);
        CallErrorHandler(ErrorLevel.Error, msg, e);
      }
      return result;
    }

    // Precondition: parser != null, initialized correctly
    private unsafe ParseStatus ContinueEntityParsing()
    {
      SaxEntityContext entContext = parser.EntityContext;
      // exactly one of these two must be != null
      TextReader reader = entContext.Reader;
      Stream stream = entContext.Stream;
      Debug.Assert(reader != null || stream != null);

      ReadBuffer read;
      if (stream != null)
        read = new StreamBufferReader(stream).Read;
      else
        read = new TextBufferReader(reader).Read;
      ParseStatus status = parser.Parse(read);
      if (status == ParseStatus.FatalError)
        ProcessExpatError(entContext.Error);
      return status;
    }

    private InputSource DefaultResolveExternalEntity(
      Uri baseUri,
      string systemId,
      out Exception error)
    {
      InputSource result = null;
      error = null;

      try {
        // must absolutize systemId if it is an URI
        Uri sysIdUri = null;
        Stream entityStream = null;
        try {
          if (baseUri == null)
            sysIdUri = new Uri(systemId);
          else
            sysIdUri = new Uri(baseUri, systemId);
        }
        catch (UriFormatException) {
          // not an URI, we assume it is a file name
          entityStream =
                new FileStream(systemId, FileMode.Open, FileAccess.Read);
        }
        if (entityStream == null) {
          WebRequest request = WebRequest.Create(sysIdUri);
          try {
            request.Timeout = Constants.UriTimeout;
          }
          catch (NotSupportedException) { }  // ignore if not supported
          WebResponse response = request.GetResponse();
          entityStream = response.GetResponseStream();
        }
        result = new ExpatStreamSource(entityStream);
        if (sysIdUri != null)
          result.SystemId = sysIdUri.AbsoluteUri;
        else
          result.SystemId = systemId;
      }
      catch (Exception e) {
        error = e;
      }
      return result;
    }

    #endregion

    #region Internal Properties and Methods

    internal ExpatAttributes Attributes
    {
      get { return attributes; }
    }

    internal ParameterEntityDecls ParamEntityDecls
    {
      get { return paramEntityDecls; }
    }

    internal void CallErrorHandler(ErrorLevel level, string message)
    {
      if (ErrorHandler != null) {
        switch (level) {
          case ErrorLevel.Warning:
            ErrorHandler.Warning(parseData.Error(message));
            break;
          case ErrorLevel.Error:
            ErrorHandler.Error(parseData.Error(message));
            break;
          case ErrorLevel.Fatal:
            ErrorHandler.FatalError(parseData.Error(message));
            break;
        }
      }
      else if (level != ErrorLevel.Warning)
        throw new SaxParseException(message);
    }

    internal void CallErrorHandler(ErrorLevel level, string message, Exception e)
    {
      if (ErrorHandler != null) {
        switch (level) {
          case ErrorLevel.Warning:
            ErrorHandler.Warning(parseData.Error(message, e));
            break;
          case ErrorLevel.Error:
            ErrorHandler.Error(parseData.Error(message, e));
            break;
          case ErrorLevel.Fatal:
            ErrorHandler.FatalError(parseData.Error(message, e));
            break;
        }
      }
      else if (level != ErrorLevel.Warning)
        throw new SaxParseException(message, e);
    }

    internal void CallErrorHandler(ErrorLevel level, string message, int code)
    {
      if (ErrorHandler != null) {
        switch (level) {
          case ErrorLevel.Warning:
            ErrorHandler.Warning(parseData.Error(message, code));
            break;
          case ErrorLevel.Error:
            ErrorHandler.Error(parseData.Error(message, code));
            break;
          case ErrorLevel.Fatal:
            ErrorHandler.FatalError(parseData.Error(message, code));
            break;
        }
      }
      else if (level != ErrorLevel.Warning)
        throw new SaxParseException(parseData.Error(message, code));
    }

    internal InputSource ResolveForeignDtd(string name)
    {
      InputSource result = null;
      if (EntityResolver != null) {
        // parser.EntityContext.BaseUri is guaranteed to be absolute
        Uri baseUri = parser.EntityContext.BaseUri;
        string bsUri = baseUri == null ? null : baseUri.AbsoluteUri;
        result = EntityResolver.GetExternalSubset(name, bsUri);
        // result == null is not an error, but an application choice
      }
      return result;
    }

    internal InputSource ResolveExternalEntity(
      string name,
      string publicId,
      Uri baseUri,
      string systemId,
      out string msg,
      out Exception error)
    {
      InputSource result = null;
      msg = null;
      error = null;

      // try entity resolver first, if one is assigned
      if (EntityResolver != null) {
        string bsUri = baseUri == null ? null : baseUri.AbsoluteUri;
        // systemId does not need to be absolutized, as we pass the base URI
        result = EntityResolver.ResolveEntity(name, publicId, bsUri, systemId);
      }
      if (result != null)
        return result;
      // still unresolved - use internal (default) entity resolver
      result = DefaultResolveExternalEntity(baseUri, systemId, out error);
      if (result == null) {
        string tmpMsg = KdsConsts.GetString(KdsRsId.CannotResolveEntity);
        msg = String.Format(tmpMsg, publicId, systemId);
      }
      return result;
    }

    #endregion

    #region IXmlReader

    // must be in sync with GetFeature()
    private readonly string[] featureNames = new string[] {
      SaxConsts.NamespacesFeature,
      SaxConsts.NamespacePrefixesFeature,
      SaxConsts.ValidationFeature,
      SaxConsts.ExternalGeneralFeature,
      SaxConsts.ExternalParameterFeature,
      SaxConsts.ResolveDtdUrisFeature,
      SaxConsts.LexicalParameterFeature,
      SaxConsts.XmlNsUrisFeature,
      SaxConsts.Xml11Feature,
      SaxConsts.UnicodeNormCheckFeature,
      SaxConsts.XmlDeclFeature,
      SaxConsts.UseExternalSubsetFeature,
      SaxConsts.ReaderControlFeature,
      KdsConsts.SkipInternalFeature,
      KdsConsts.ParseUnlessStandaloneFeature,
      KdsConsts.ParameterEntitiesFeature,
      KdsConsts.StandaloneErrorFeature
    };

    private int FeatureIndex(string feature)
    {
      int result = Array.IndexOf(featureNames, feature);
      if (result < featureNames.GetLowerBound(0)) {
        string msg = SaxRes.GetString(SaxRsId.FeatureNotRecognized);
        throw new ArgumentException(String.Format(msg, feature), "name");
      }
      return result;
    }

    /// <summary>Returns if named feature is turned on or not.</summary>
    /// <remarks>Supported features are:
    ///
    /// <list type="table">
    ///   <listheader>
    ///     Standard features - prefixed with "http://xml.org/sax/features/"
    ///     <term>Name</term>
    ///     <description>Properties</description>
    ///   </listheader>
    ///   <item>
    ///     <term>namespaces</term>
    ///     <description>
    ///       Access: read/write, default true.<br/>
    ///       Should be set *before* parsing is started. If set later, it will
    ///       only affect the reporting of QNames, that is, if namespaces was
    ///       true at the beginning, then QName/Prefix reporting can be turned
    ///       off during parsing, but namespace processing will stay active.
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>namespace-prefixes</term>
    ///     <description>
    ///       Access: read-only, value depends on namespaces feature.<br/>
    ///       The underlying Expat parser never reports xmlns attributes when
    ///       namespace processing is turned on, so this feature is set to false
    ///       in this case even though prefixes (in QNames) would still be reported.
    ///       Conversely, when namespace processing is turned off then this feature
    ///       is automatically set to true.
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>validation</term>
    ///     <description>
    ///       Access: read/write, fixed false.
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>external-general-entities</term>
    ///     <description>
    ///       Access: read/write, default false.
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>external-parameter-entities</term>
    ///     <description>
    ///       Access: read/write, default false.
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>resolve-dtd-uris</term>
    ///     <description>
    ///       Access: read/write, default false.
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>lexical-handler/parameter-entities</term>
    ///     <description>
    ///       Access: read/write, default true.<br/>
    ///       With limitations - See remarks regarding internal entities
    ///       and <see cref="ILexicalHandler"/> on <see cref="ExpatReader"/>.
    ///       It is not certain that the way this feature is implemented
    ///       conforms to SAX2, but it works the same way as in MSXML4.
    ///       It seems that if it is turned off then parameter entities should
    ///       still be reported if skipped, but this is not done here since
    ///       internally this feature is equivalent to the parameter-entities
    ///       extension feature which turns parameter entity parsing on or off.
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>xmlns-uris</term>
    ///     <description>
    ///       Access: read/write, fixed false.<br/>
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>xml-1.1</term>
    ///     <description>
    ///       Access: read-only, fixed false.<br/>
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>unicode-normalization-checking</term>
    ///     <description>
    ///       Access: read/write, fixed false.<br/>
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>xml-declaration</term>
    ///     <description>
    ///       Access: read-only, fixed true.<br/>
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>use-external-subset</term>
    ///     <description>
    ///       Access: read-only, fixed true.<br/>
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>reader-control</term>
    ///     <description>
    ///       Access: read-only, default depends on compile options.<br/>
    ///       Returns if the Suspend()/Resume() functionality is implemented.
    ///       This is true when compiled with EXPAT_1_95_8_UP defined
    ///       and version 1.95.8 (or later) of the Expat library is used.
    ///     </description>
    ///   </item>
    /// </list>
    ///
    /// <list type="table">
    ///   <listheader>
    ///     Extension features - prefixed with "http://kd-soft.net/sax/features/"
    ///     <term>Name</term>
    ///     <description>Properties</description>
    ///   </listheader>
    ///   <item>
    ///     <term>parameter-entities</term>
    ///     <description>
    ///       Access: read/write, default true.<br/>
    ///       Turns parameter entity parsing on or off. This affects all of the
    ///       external subset, as well as parameter entities in the internal
    ///       subset. The rest of the internal subset is still being processed.
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>skip-internal-entities</term>
    ///     <description>
    ///       Access: read/write, default false.<br/>
    ///       Skip or (silently) expand internal entities (parameter and general).
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>parse-unless-standalone</term>
    ///     <description>
    ///       Access: read/write, default false.<br/>
    ///       When turned on external parameter entities will not be parsed
    ///       if the XML declaration specifies standalone = "yes".
    ///     </description>
    ///   </item>
    ///   <item>
    ///     <term>standalone-error</term>
    ///     <description>
    ///       Access: read/write, default false.<br/>
    ///       Determines if the parser returns an error, when it encounters
    ///       an external subset or a reference to a parameter entity, but
    ///       the document has standalone="yes" in the XML declaration.
    ///     </description>
    ///   </item>
    /// </list>
    ///
    /// <para>
    ///   Note: "read/write, fixed false" means that the feature can be set,
    ///   but only to the value false, otherwise an exception will be thrown.
    /// </para>
    /// </remarks>
    public bool GetFeature(string name)
    {
      switch (FeatureIndex(name)) {
        case 0:  // http://xml.org/sax/features/namespaces
          return Namespaces;
        case 1:  // http://xml.org/sax/features/namespace-prefixes
          return NamespacePrefixes;
        case 2:  // http://xml.org/sax/features/validation
          return Validation;
        case 3:  // http://xml.org/sax/features/external-general-entities
          return ExternalGeneral;
        case 4:  // http://xml.org/sax/features/external-parameter-entities
          return ExternalParameter;
        case 5:  // http://xml.org/sax/features/resolve-dtd-uris
          return ResolveDtdUris;
        case 6:  // http://xml.org/sax/features/lexical-handler/parameter-entities
          return ParameterEntities;
        case 7:  // http://xml.org/sax/features/xmlns-uris
          return XmlNsUris;
        case 8:  // http://xml.org/sax/features/xml-1.1
          return Xml11;
        case 9:  // http://xml.org/sax/features/unicode-normalization-checking
          return UnicodeNormCheck;
        case 10: // http://xml.org/sax/features/xml-declaration
          return XmlDecl;
        case 11: // http://xml.org/sax/features/use-external-subset
          return UseExternalSubset;
        case 12: // http://xml.org/sax/features/reader-control
          return ReaderControl;
        case 13: // http://kd-soft.net/sax/features/skip-internal-entities
          return SkipInternal;
        case 14: // http://kd-soft.net/sax/features/parse-unless-standalone
          return ParseUnlessStandalone;
        case 15: // http://kd-soft.net/sax/features/parameter-entities
          return ParameterEntities;
        case 16: // http://kd-soft.net/sax/features/standalone-error
          return StandaloneError;
        default:
          string msg = SaxRes.GetString(SaxRsId.FeatureNotSupported);
          throw new NotSupportedException(String.Format(msg, name));
      }
    }

    /// <summary>Turns named feature on or off.</summary>
    /// <remarks>See remarks on <see cref="GetFeature"/>.</remarks>
    public void SetFeature(string name, bool value)
    {
      switch (FeatureIndex(name)) {
        case 0:  // http://xml.org/sax/features/namespaces
          Namespaces = value;
          return;
        case 1:  // http://xml.org/sax/features/namespace-prefixes
          NamespacePrefixes = value;
          return;
        case 2:  // http://xml.org/sax/features/validation
          Validation = value;
          return;
        case 3:  // http://xml.org/sax/features/external-general-entities
          ExternalGeneral = value;
          return;
        case 4:  // http://xml.org/sax/features/external-parameter-entities
          ExternalParameter = value;
          return;
        case 5:  // http://xml.org/sax/features/resolve-dtd-uris
          ResolveDtdUris = value;
          return;
        case 6:  // http://xml.org/sax/features/lexical-handler/parameter-entities
          ParameterEntities = value;
          return;
        case 7:  // http://xml.org/sax/features/xmlns-uris
          XmlNsUris = value;
          return;
        case 8:  // http://xml.org/sax/features/xml-1.1
          break;
        case 9:  // http://xml.org/sax/features/unicode-normalization-checking
          UnicodeNormCheck = value;
          return;
        case 10: // http://xml.org/sax/features/xml-declaration
          break;
        case 11: // http://xml.org/sax/features/use-external-subset
          break;
        case 12: // http://xml.org/sax/features/reader-control
          break;
        case 13: // http://kd-soft.net/sax/features/skip-internal-entities
          SkipInternal = value;
          return;
        case 14: // http://kd-soft.net/sax/features/parse-unless-standalone
          ParseUnlessStandalone = value;
          return;
        case 15: // http://kd-soft.net/sax/features/parameter-entities
          ParameterEntities = value;
          return;
        case 16: // http://kd-soft.net/sax/features/standalone-error
          StandaloneError = value;
          return;
        default:
          break;
      }
      string msg = SaxRes.GetString(SaxRsId.FeatureWriteNotSupported);
      throw new NotSupportedException(String.Format(msg, name));
    }

    /// <summary>Returns interface to named property.</summary>
    /// <remarks>Supported properties are:
    /// <list type="table">
    ///   <listheader>
    ///     Extension properties - prefixed with "http://kd-soft.net/sax/properties/"
    ///     <term>Name</term>
    ///     <description>Properties</description>
    ///   </listheader>
    ///   <item>
    ///     <term>default-handler</term>
    ///     <description>
    ///       Value access: read/write.<br/>
    ///       The default handler receives events for which no handler is set.
    ///     </description>
    ///   </item>
    /// </list>
    /// </remarks>
    public IProperty<T> GetProperty<T>(string name)
    {
      switch(name) {
        case Kds.Xml.Sax.Constants.DefaultHandlerProperty:
          return (IProperty<T>)defHandlerProp;
        default:
          return null;
      }
    }

    /// <remarks>When <see cref="IEntityResolver.GetExternalSubset"/> is called
    /// for a document without a DOCTYPE declaration, then the Name argument
    /// will be empty since Expat does not read ahead to determine the name
    /// of the root element. This behaviour does not follow the SAX2 specs.
    /// However, it should not be a problem because the application which
    /// provides the external subset should rather use information from the
    /// application context to supply the correct DTD, and not rely on the
    /// Name argument.</remarks>
    public IEntityResolver EntityResolver
    {
      get { return entityResolver; }
      set { entityResolver = value; }
    }

    public IDtdHandler DtdHandler
    {
      get { return dtdHandler; }
      set {
        dtdHandler = value;
        SetDtdHandlerForParsing(value != null);
      }
    }

    public IContentHandler ContentHandler
    {
      get { return contentHandler; }
      set {
        contentHandler = value;
        SetContentHandlerForParsing(value != null);
      }
    }

    public ILexicalHandler LexicalHandler
    {
      get { return lexicalHandler; }
      set {
        lexicalHandler = value;
        SetLexicalHandlerForParsing(value != null);
      }
    }

    public IDeclHandler DeclHandler
    {
      get { return declHandler; }
      set { 
        declHandler = value;
        SetDeclHandlerForParsing(value != null);
      }
    }

    public IErrorHandler ErrorHandler
    {
      get { return errorHandler; }
      set { errorHandler = value; }
    }

    // source must be InputSource<Stream> or InputSource<TextReader> - not checked!
    private void ParseSource(InputSource source, bool isStreamSource)
    {
      try {
        ParseStatus status = StartParsing(source, isStreamSource);
        if (status != ParseStatus.Suspended)
          FinishParsing();
      }
      catch {
        FinishParsing();
        throw;
      }
    }

    public void Parse(InputSource input)
    {
      if (input == null)
        throw new ArgumentNullException("input");
      bool isStreamSource = input is InputSource<Stream>;
      // we can deal with InputSource<Stream> and InputSource<TextReader> only,
      // for everything else we will use the SystemId to resolve the entity
      if (isStreamSource || (input is InputSource<TextReader>))
        ParseSource(input, isStreamSource);
      else {
        string msg = Constants.GetString(RsId.StreamReaderInputSourceOnly);
        if (input.SystemId != String.Empty) {
          CallErrorHandler(ErrorLevel.Warning, msg);
          Parse(input.SystemId);
        }
        else
          throw new ArgumentException(msg);
      }
    }

    public void Parse(string systemId)
    {
      string msg;
      Exception error;
      InputSource input = DefaultResolveExternalEntity(null, systemId, out error);
      if (input == null) {
        if (error == null) {
          msg = KdsConsts.GetString(KdsRsId.CannotResolveEntity);
          throw new SaxParseException(String.Format(msg, "", systemId));
        }
        else
          throw error;
      }
      bool isStreamSource = input is InputSource<Stream>;
      // we can deal with StreamInputSource and ReaderInputSource only
      if (isStreamSource || (input is InputSource<TextReader>))
        ParseSource(input, isStreamSource);
      else {
        msg = Constants.GetString(RsId.StreamReaderInputSourceOnly);
        throw new SaxParseException(msg);
      }
    }

    #endregion

    #region SAX2 Extension Properties and Features exposed as C# Properties

    public bool Namespaces
    {
      get { return namespaces; }
      set {
        SetNamespacesForParsing(value);
        namespaces = value;
        // in SAXExpat namespace-prefixes depends on namespaces
        namespacePrefixes = !value;
      }
    }

    public bool NamespacePrefixes
    {
      get { return namespacePrefixes; }
      set {
        // in SAXExpat namespace-prefixes depends on namespaces
        if (value != namespacePrefixes) {
          string msg = Constants.GetString(RsId.NamespacePrefixesDepends);
          throw new NotSupportedException(msg);
        }
      }
    }

    public bool Validation
    {
      get { return false; }
      set {
        if (value) {
          string msg = SaxRes.GetString(SaxRsId.FeatureNotSupported);
          msg = String.Format(msg, SaxConsts.ValidationFeature, value);
          throw new NotSupportedException(msg);
        }
      }
    }

    public bool ExternalGeneral
    {
      get { return externalGeneral; }
      set { externalGeneral = value; }
    }

    public bool ExternalParameter
    {
      get { return externalParameter; }
      set { externalParameter = value; }
    }

    public bool AttributeDeclared
    {
      get { return true; }
    }

    public bool XmlDecl
    {
      get { return true; }
    }

    public bool UseExternalSubset
    {
      get { return true; }
    }

    public bool IsStandalone
    {
      get { return DocStandalone == XmlDocStandalone.Yes; }
    }

    public bool ResolveDtdUris
    {
      get { return resolveDtdUris; }
      set { resolveDtdUris = value; }
    }

    public bool ReaderControl
    {
      get {
      #if EXPAT_1_95_8_UP
        return true;
      #else
        return false;
      #endif
      }
    }

#if EXPAT_1_95_8_UP
    public void Suspend()
    {
      if (!Parsing) {
        string msg = KdsConsts.GetString(KdsRsId.IllegalWhenNotParsing);
        throw new SaxParseException(String.Format(msg, "Suspend"));
      }
      if (!parser.Suspend()) {
        switch (parser.EntityContext.Error) {
          case XMLError.FINISHED:    // parser finished - ignore error
            goto case XMLError.SUSPEND_PE;
          case XMLError.SUSPEND_PE:  // parser not suspendable - ignore error
            return;
        }
        throw new SaxException(LibExpat.XMLErrorString(parser.EntityContext.Error));
      }
    }

    public void Abort()
    {
      if (!Parsing) {
        string msg = KdsConsts.GetString(KdsRsId.IllegalWhenNotParsing);
        throw new SaxParseException(String.Format(msg, "Abort"));
      }
      // no cleanup here, as we are in the middle of a call-back
      if (!parser.Abort()) {
        // if we are at end of entity, ignore error
        if (parser.EntityContext.Error == XMLError.FINISHED)
          return;
        throw new SaxException(LibExpat.XMLErrorString(parser.EntityContext.Error));
      }
    }

    public void Resume()
    {
      if (!Parsing) {
        string msg = KdsConsts.GetString(KdsRsId.IllegalWhenNotParsing);
        throw new SaxParseException(String.Format(msg, "Resume"));
      }
      try {
        ParseStatus status = parser.Resume();
        switch (status) {
          case ParseStatus.Suspended:
            break;
          case ParseStatus.FatalError:
            if (parser.EntityContext.Error != XMLError.NONE)
              ProcessExpatError(parser.EntityContext.Error);
            FinishParsing();
            break;
          default:
            FinishParsing();
            break;
        }
      }
      catch {
        FinishParsing();
        throw;
      }
    }

    public XmlReaderStatus Status
    {
      get {
        if (!Parsing)
          return XmlReaderStatus.Ready;
        else {
          switch (parser.ParsingStatus.Parsing) {
            case XMLParsing.INITIALIZED:
              return XmlReaderStatus.Ready;
            case XMLParsing.PARSING:
              return XmlReaderStatus.Parsing;
            case XMLParsing.FINISHED:
              return XmlReaderStatus.Parsing;
            case XMLParsing.SUSPENDED:
              return XmlReaderStatus.Suspended;
            default:
              return XmlReaderStatus.Parsing;
          }
        }
      }
    }
#else
    public void Suspend()
    {
      throw new NotSupportedException();
    }

    public void Abort()
    {
      throw new NotSupportedException();
    }

    public void Resume()
    {
      throw new NotSupportedException();
    }

    public XmlReaderStatus Status
    {
      get {
        if (Parsing)
          return XmlReaderStatus.Parsing;
        else
          return XmlReaderStatus.Ready;
      }
    }
#endif

    #endregion

    #region SAXExpat Extension Properties and Features exposed as C# Properties

    public IDefaultHandler DefaultHandler
    {
      get { return (IDefaultHandler)(defHandlerProp.Value); }
      set { defHandlerProp.Value = value; }
    }

    /// <summary>Base URI of the entity currently being parsed.</summary>
    /// <remarks>Must be an absolute URI.</remarks>
    public unsafe string BaseUri
    {
      get {
        CheckParsing(RsId.AccessingBaseUri);
        Uri baseUri = parser.EntityContext.BaseUri;
        string result = baseUri == null ? null : baseUri.AbsoluteUri;
        return result;
      }
      set {
        CheckParsing(RsId.AccessingBaseUri);
        if (value == null)
          parser.EntityContext.BaseUri = null;
        else
          // we will get an exception if value is not an absolute URI
          parser.EntityContext.BaseUri = new Uri(value);
      }
    }

    public bool ParameterEntities
    {
      get { return parameterEntities; }
      set {
        parameterEntities = value;
        SetParamEntitiesForParsing(value);
      }
    }

    public bool XmlNsUris
    {
      get { return false; }
      set {
        if (value) {
          string msg = SaxRes.GetString(SaxRsId.FeatureNotSupported);
          msg = String.Format(msg, SaxConsts.XmlNsUrisFeature, value);
          throw new NotSupportedException(msg);
        }
      }
    }

    public bool Xml11
    {
      get { return false; }
    }

    public bool UnicodeNormCheck
    {
      get { return false; }
      set {
        if (value) {
          string msg = SaxRes.GetString(SaxRsId.FeatureNotSupported);
          msg = String.Format(msg, SaxConsts.UnicodeNormCheckFeature, value);
          throw new NotSupportedException(msg);
        }
      }
    }

    public bool SkipInternal
    {
      get { return parser.SkipInternal; }
      set { SetDefHandlerForParsing(DefaultHandler != null, value); }
    }

    public bool ParseUnlessStandalone
    {
      get { return parseUnlessStandalone; }
      set {
        parseUnlessStandalone = value;
        SetParamEntitiesForParsing(value);
      }
    }

    public bool StandaloneError
    {
      get { return standaloneError; }
      set { standaloneError = value; }
    }

    public XmlDocStandalone DocStandalone
    {
      get {
        if (!Parsing) {
          string msg = SaxRes.GetString(SaxRsId.FeatureWhenParsing);
          throw new NotSupportedException(msg);
        }
        if (parseData.Standalone < 0)
          return XmlDocStandalone.Undefined;
        else if (parseData.Standalone > 0)
          return XmlDocStandalone.Yes;
        else
          return XmlDocStandalone.No;
      }
    }

    public string ExpatVersion
    {
      get { return LibExpat.XMLExpatVersion(); }
    }

    public string ForeignDoctypeName
    {
      get { return foreignDoctypeName; }
      set { foreignDoctypeName = value; }
    }

    /// <summary>String table used for string interning.</summary>
    /// <remarks>Must not be <c>null</c>. Should not be set while parsing.</remarks>
    [CLSCompliant(false)]
    public StringTable StrTable
    {
      get { return strTable; }
      set {
        if (value == null)
          throw new ArgumentNullException("StrTable");
        strTable = value;
      }
    }

    /// <summary>Expat specific content model structure.</summary>
    /// <remarks>Only valid in ElementDecl() handler call-back.</remarks>
    [CLSCompliant(false)]
    public unsafe XMLContent* ContentModel
    {
      get { return parseData.ContentModel; }
    }

    #endregion

    #region Expat Handlers

    /* ElementDeclHandler */

    // helper routine to convert XMLContent structure back to string
    private static unsafe void
    WriteModelNode(StringBuilder builder, StringTable strTable, XMLContent* node)
    {
      uint num;
      XMLContent* child;
      char sepChar = '|';
      switch ((*node).Type) {
        case XMLContentType.EMPTY:
          builder.Append("EMPTY");
          break;
        case XMLContentType.ANY:
          builder.Append("ANY");
          break;
        case XMLContentType.NAME:
          // we assume that (*node).Name != null
          string nameStr = strTable.Intern((*node).Name);
          builder.Append(nameStr);
          break;
        case XMLContentType.CHOICE:
          sepChar = ',';
          goto case XMLContentType.SEQ;
        case XMLContentType.SEQ:
          builder.Append(KdsConsts.LeftPar);
          num = (*node).NumChildren;
          Debug.Assert(num > 0);
          // sepChar is either '|' or ','
          child = (*node).Children;
          WriteModelNode(builder, strTable, child);
          child++;
          num--;
          while (num > 0) {
            builder.Append(sepChar);
            WriteModelNode(builder, strTable, child);
            child++;
            num--;
          }
          builder.Append(KdsConsts.RightPar);
          break;
        case XMLContentType.MIXED:
          builder.Append("(#PCDATA");
          num = (*node).NumChildren;
          child = (*node).Children;
          while (num > 0) {
            builder.Append('|');
            WriteModelNode(builder, strTable, child);
            child++;
            num--;
          }
          builder.Append(KdsConsts.RightPar);
          break;
      }
      switch ((*node).Quant) {
        case XMLContentQuant.OPT:
          builder.Append('?');
          break;
        case XMLContentQuant.REP:
          builder.Append('*');
          break;
        case XMLContentQuant.PLUS:
          builder.Append('+');
          break;
      }
    }

    private static unsafe void
    ElementDeclHandlerImpl(IntPtr userData, char* name, XMLContent* model)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      IDeclHandler declHandler = reader.DeclHandler;
      // not necessary - callback only set when DeclHandler set
      // if (declHandler == null) return;
      try {
        reader.parseData.ContentModel = model;
        StringBuilder builder = reader.parseData.ContentBuilder;
        builder.Length = 0;
        WriteModelNode(builder, reader.StrTable, model);
        string nameStr = reader.StrTable.Intern(name);
        declHandler.ElementDecl(nameStr, builder.ToString());
      }
      finally {
        // was allocated by Expat, but we own it and must de-allocate it
        reader.parser.FreeContentmodel(model);
        reader.parseData.ContentModel = null;
      }
    }

    /* AttlistDeclHandler */

    /* Note: DTDs don't know about namespaces, therefore the elName and attName
     * arguments have to be taken literally.
     */
    private static unsafe void
    AttlistDeclHandlerImpl(IntPtr userData,
                           char* elName,
                           char* attName,
                           char* attType,
                           char* dflt,
                           int isRequired)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      IDeclHandler declHandler = reader.DeclHandler;
      // not necessary - callback only set when DeclHandler set
      // if (declHandler == null) return;
      string dfltType;
      // determine default type
      if (isRequired != 0) {
        if (dflt == null)
          dfltType = "#REQUIRED";
        else
          dfltType = "#FIXED";
      }
      else {
        if (dflt == null)
          dfltType = "#IMPLIED";
        else
          dfltType = null;
      }

      StringTable strTable = reader.StrTable;
      string elNameStr = strTable.Intern(elName);
      string attNameStr = strTable.Intern(attName);
      string attTypeStr = strTable.Intern(attType);
      string dfltStr = (dflt == null) ? null : strTable.Intern(dflt);
      reader.Attributes.AttDecls.Add(
                        elNameStr, attNameStr, attTypeStr, dfltType, dfltStr);
      declHandler.AttributeDecl(
                        elNameStr, attNameStr, attTypeStr, dfltType, dfltStr);
    }

    /* XmlDeclHandler */

    /* Notes:
     * - Version and Encoding are reported through the new ILocator interface,
     *   Standalone is reported through the ILocator.EntityType property.
     * - Called when the XML or Text declarations are parsed. The value of
     *   standalone will be 1 if the document is declared standalone, 0 if it is
     *   declared not to be standalone, or -1 if the standalone clause was omitted.
     */
    private static unsafe void
    XmlDeclHandlerImpl(IntPtr userData,
                       char* version,
                       char* encoding,
                       int standalone)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // The docs say that version == null for Text declarations (which occur
      // in external entities), but this does not seem to be correct, so we
      // use a different test to check if we have an XML or a Text declaration.
      SaxEntityContext entSC = reader.parser.EntityContext;
      if (entSC.Parent == null)
        reader.parseData.Standalone = standalone;
      entSC.Version = (version == null) ?
                        null : reader.StrTable.Intern(version);
      // The declaration's encoding cannot override the input source!
      if (entSC.Encoding == null)  // no encoding set by input source
        entSC.Encoding = (encoding == null) ?
                           null : reader.StrTable.Intern(encoding);
    }

    /* StartElementHandler */

    /* Note: the atts argument is only valid for this call. */
    private static unsafe void
    StartElementHandlerImpl(IntPtr userData, char* name, char** atts)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // not necessary - callback only set when ContentHandler set
      // if (reader.ContentHandler == null) return;
      string uri, locName, qName;
      int specAttCount = reader.parser.SpecifiedAttributeCount;
      ExpatUtils.ParseNameSax(name, reader.StrTable, out uri, out locName, out qName);
      ExpatAttributes attributes = reader.Attributes;
      attributes.Initialize(qName, atts, specAttCount);
      reader.ContentHandler.StartElement(uri, locName, qName, attributes);
    }

    /* EndElementHandler */

    private static unsafe void
    EndElementHandlerImpl(IntPtr userData, char* name)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // not necessary - callback only set when ContentHandler set
      // if (reader.ContentHandler == null) return;
      string uri, locName, qName;
      ExpatUtils.ParseNameSax(name, reader.StrTable, out uri, out locName, out qName);
      reader.ContentHandler.EndElement(uri, locName, qName);
    }

    /* CharacterDataHandler */

    private static unsafe void
    CharacterDataHandlerImpl(IntPtr userData, char* s, int len)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // not necessary - callback only set when ContentHandler set
      // if (reader.ContentHandler == null) return;
      char[] charBuf = reader.parseData.GetCharBuffer(len);
      Marshal.Copy((IntPtr)s, charBuf, 0, len);
      reader.ContentHandler.Characters(charBuf, 0, len);
    }

    /* ProcessingInstructionHandler */

    /*  target and data are null terminated */
    private static unsafe void
    ProcessingInstructionHandlerImpl(IntPtr userData, char* target, char* data)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // not necessary - callback only set when ContentHandler set
      // if (reader.ContentHandler == null) return;
      StringTable strTable = reader.StrTable;
      string targetStr = strTable.Intern(target);
      string dataStr = (data == null) ? null : strTable.Intern(data);
      reader.ContentHandler.ProcessingInstruction(targetStr, dataStr);
    }

    /* CommentHandler */

    /* data is null terminated */
    private static unsafe void
    CommentHandlerImpl(IntPtr userData, char* data)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // not necessary - callback only set when LexicalHandler set
      // if (reader.LexicalHandler == null) return;
      int len = StringUtils.StrLen(data);
      char[] charBuf = reader.parseData.GetCharBuffer(len);
      Marshal.Copy((IntPtr)data, charBuf, 0, len);
      reader.LexicalHandler.Comment(charBuf, 0, len);
    }

    /* StartCdataSectionHandler */

    private static void
    StartCdataSectionHandlerImpl(IntPtr userData)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // not necessary - callback only set when LexicalHandler set
      // if (reader.LexicalHandler == null) return;
      reader.LexicalHandler.StartCData();
    }

    /* EndCdataSectionHandler */

    private static void
    EndCdataSectionHandlerImpl(IntPtr userData)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // not necessary - callback only set when LexicalHandler set
      // if (reader.LexicalHandler == null) return;
      reader.LexicalHandler.EndCData();
    }

    /* DefaultHandler */

    private static unsafe void
    DefaultHandlerImpl(IntPtr userData, char* s, int len)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // not necessary - callback only set when DefaultHandler set
      // if (reader.DefaultHandler == null) return;
      char[] charBuf = reader.parseData.GetCharBuffer(len);
      Marshal.Copy((IntPtr)s, charBuf, 0, len);
      reader.DefaultHandler.UnhandledData(charBuf, 0, len);
    }

    /* StartDoctypeDeclHandler */

    /* Note: not sure what to do with HasInternalSubset (how to pass or store) */
    private static unsafe void
    StartDoctypeDeclHandlerImpl(IntPtr userData,
                                char* doctypeName,
                                char* systemId,
                                char* publicId,
                                int hasInternalSubset)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // normalize empty strings
      string pubId = null;
      string sysId = null;
      string doctype = reader.StrTable.Intern(doctypeName);

      if (publicId != null && *publicId != Constants.NullChar)
        pubId = new string(publicId);
      if (systemId != null && *systemId != Constants.NullChar)
        sysId = new string(systemId);

      reader.parseData.HasDoctypeDecl = true;
      if (systemId == null) {
        InputSource dtdSource = reader.ResolveForeignDtd(doctype);
        reader.parseData.ForeignDtdSource = dtdSource;
        if (dtdSource != null) {
          pubId = dtdSource.PublicId;
          sysId = dtdSource.SystemId;
        }
      }
      // If publicId == null *and* systemId == null, then we don't have
      // an external subset, but ExternalEntityRefHandler may still be
      // called for a foreign DTD, so maybe we should store the entity
      // with a dummy system id.
      if (pubId == null && sysId == null)
        sysId = Constants.ForeignDtdId;
      reader.ParamEntityDecls.Add(KdsConsts.XmlDtdName, pubId, sysId);
      ILexicalHandler lexHandler = reader.LexicalHandler;
      if (lexHandler == null) return;
      // we report the "declared", not absolutized, system identifier
      lexHandler.StartDtd(doctype, pubId, sysId);
    }

    /* EndDoctypeDeclHandler */

    private static void
    EndDoctypeDeclHandlerImpl(IntPtr userData)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      ILexicalHandler lexHandler = reader.LexicalHandler;
      if (lexHandler == null) return;
      lexHandler.EndDtd();
    }

    /* EntityDeclHandler */

    private static unsafe void
    EntityDeclHandlerImpl(IntPtr userData,
                          char* entityName,
                          int isParameterEntity,
                          char* value,
                          int valueLen,
                          char* baseUri,
                          char* systemId,
                          char* publicId,
                          char* notationName)
    {
      int entLen = (entityName == null) ? 0 : StringUtils.StrLen(entityName);
      Debug.Assert(entLen > 0);
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // normalize empty strings
      string pubId = null;
      string sysId = null;
      if (publicId != null && *publicId != Constants.NullChar)
        pubId = new string(publicId);
      if (systemId != null && *systemId != Constants.NullChar)
        sysId = new string(systemId);

      string entNameStr;
      if (isParameterEntity == 0) {  // general entity
        entNameStr = reader.StrTable.Intern(entityName, entLen);
      }
      else {  // parameter entity
        char[] charBuf = reader.parseData.GetCharBuffer(entLen + 1);
        charBuf[0] = '%';
        Marshal.Copy((IntPtr)entityName, charBuf, 1, entLen);
        entNameStr = reader.StrTable.Intern(charBuf, 0, entLen + 1);
        if (sysId != null)
          // we report the "declared", not absolutized, system identifier
          reader.ParamEntityDecls.Add(entNameStr, pubId, sysId);
      }

      if (notationName != null) { // unparsed entity declaration
        IDtdHandler dtdHandler = reader.DtdHandler;
        if (dtdHandler == null)
          return;
        // must absolutize the system identifier if it is an URI and
        // the resolve-dtd-uris feature is turned on
        if (sysId != null && reader.ResolveDtdUris)
          sysId = reader.ResolveSystemId(
                            sysId, RsId.UnparsedEntityDeclError, entNameStr);
        string notationStr = reader.StrTable.Intern(notationName);
        dtdHandler.UnparsedEntityDecl(entNameStr, pubId, sysId, notationStr);
      }
      else {
        IDeclHandler declHandler = reader.DeclHandler;
        if (declHandler == null)
          return;
        if (value != null) {
          string valueStr = new string(value, 0, valueLen);
          declHandler.InternalEntityDecl(entNameStr, valueStr);
        }
        else {
          // must absolutize the system identifier if it is an URI and
          // the resolve-dtd-uris feature is turned on
          if (sysId != null && reader.ResolveDtdUris)
            sysId = reader.ResolveSystemId(
                             sysId, RsId.ExternalEntityDeclError, entNameStr);
          declHandler.ExternalEntityDecl(entNameStr, pubId, sysId);
        }
      }
    }

    /* NotationDeclHandler */

    private static unsafe void
    NotationDeclHandlerImpl(IntPtr userData,
                            char* notationName,
                            char* baseUri,
                            char* systemId,
                            char* publicId)
    {
      Debug.Assert(notationName != null);
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // not necessary - callback only set when DtdHandler set
      // if (reader.DtdHandler == null) return;

      // normalize empty strings
      string pubId = null;
      string sysId = null;
      if (publicId != null && *publicId != Constants.NullChar)
        pubId = new string(publicId);
      if (systemId != null && *systemId != Constants.NullChar)
        sysId = new string(systemId);
      string notationStr = reader.StrTable.Intern(notationName);
      // must absolutize the system identifier if it is an URI and
      // the resolve-dtd-uris feature is turned on
      if (sysId != null && reader.ResolveDtdUris)
        sysId = reader.ResolveSystemId(
                             sysId, RsId.NotationDeclError, notationStr);
      reader.DtdHandler.NotationDecl(notationStr, pubId, sysId);
    }

    /* StartNamespaceDeclHandler */

    private static unsafe void
    StartNamespaceDeclHandlerImpl(IntPtr userData, char* prefix, char* uri)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // not necessary - callback only set when ContentHandler set
      // if (reader.ContentHandler == null) return;
      string prefixStr = reader.StrTable.Intern(prefix);
      string uriStr = reader.StrTable.Intern(uri);
      reader.ContentHandler.StartPrefixMapping(prefixStr, uriStr);
    }

    /* EndNamespaceDeclHandler */

    private static unsafe void
    EndNamespaceDeclHandlerImpl(IntPtr userData, char* prefix)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // not necessary - callback only set when ContentHandler set
      // if (reader.ContentHandler == null) return;
      string prefixStr = reader.StrTable.Intern(prefix);
      reader.ContentHandler.EndPrefixMapping(prefixStr);
    }

    /* NotStandaloneHandler */

    /* Note: This determines if the Expat parser returns an
     * XML_ERROR_NOT_STANDALONE error when it encounters an external subset
     * or a reference to a parameter entity, but does have standalone set
     * to "yes" in an XML declaration.
     */
    private static int
    NotStandaloneHandlerImpl(IntPtr userData)
    {
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      if (reader.StandaloneError)
        return 0;  // this causes the parser to return an error
      else
        return 1;
    }

    /* SkippedEntityHandler */

    private static unsafe void
    SkippedEntityHandlerImpl(IntPtr userData,
                             char* entityName,
                             int isParameterEntity)
    {
      int entLen = (entityName == null) ? 0 : StringUtils.StrLen(entityName);
      Debug.Assert(entLen > 0);
      ExpatReader reader = ((SaxExpatParser)((GCHandle)userData).Target).UserData;
      // not necessary - callback only set when ContentHandler set
      // if (reader.ContentHandler == null) return;

      string entNameStr;
      if (isParameterEntity == 0) {  // general entity
        entNameStr = reader.StrTable.Intern(entityName, entLen);
      }
      else {  // parameter entity
        char[] charBuf = reader.parseData.GetCharBuffer(entLen + 1);
        charBuf[0] = '%';
        Marshal.Copy((IntPtr)entityName, charBuf, 1, entLen);
        entNameStr = reader.StrTable.Intern(charBuf, 0, entLen + 1);
      }
      reader.ContentHandler.SkippedEntity(entNameStr);
    }

    #endregion
  }

  /**<summary>Class for internal use in <see cref="ExpatReader"/>. Groups all
   * entity specific fields that are used during the parsing process.</summary>
   */
  internal class SaxEntityContext: EntityParseContext<SaxEntityContext, SaxExpatParser, ExpatReader>
  {
    private Stream stream;
    private TextReader reader;
    private bool owned;  // indicates if we own the stream or reader

    private string encoding;
    private string publicId;
    private string systemId;
    private Uri baseUri;

    private string name;
    private bool isParameterEntity;
    private string version;

    // source must be IInputSource<Stream> or IInputSource<TextReader>
    internal unsafe void Init(
      string name,
      bool isParameterEntity,
      InputSource source,
      bool isStreamSource,
      bool owned)
    {
      this.owned = owned;
      if (isStreamSource) {
        InputSource<Stream> streamSource = (InputSource<Stream>)source;
        stream = streamSource.Source;
        if (stream == null) {
          string msg = KdsConsts.GetString(KdsRsId.InvalidInputStream);
          throw new SaxException(msg);
        }
      }
      else {
        InputSource<TextReader> readerSource = (InputSource<TextReader>)source;
        reader = readerSource.Source;
        if (reader == null) {
          string msg = KdsConsts.GetString(KdsRsId.InvalidInputReader);
          throw new SaxException(msg);
        }
      }
      this.name = name;
      this.isParameterEntity = isParameterEntity;
      this.encoding = source.Encoding;
      this.publicId = source.PublicId;
      // InputSource.SystemId is guaranteed to be null or an absolute URI
      this.systemId = source.SystemId;
      this.baseUri = (systemId == null) ? null : new Uri(systemId);
    }

    // will be called before Reset()
    protected override void Cleanup()
    {
      base.Cleanup();
      if (owned) {
        if (stream != null)
          stream.Close();
        else if (reader != null)
          reader.Close();
      }
    }

    protected override unsafe bool Start(
      char* context,
      char* baseUri,
      char* systemId, char* publicId,
      out ReadBuffer read,
      out string encoding)
    {
      ExpatReader reader = Parser.UserData;
      read = null;
      encoding = null;

      string entName = null;
      string pubId = null;
      string sysId = null;
      InputSource inSource = null;
      // normalize empty strings
      if (publicId != null && *publicId != Constants.NullChar)
        pubId = new string(publicId);
      if (systemId != null && *systemId != Constants.NullChar)
        sysId = new string(systemId);

      bool isParameterEntity = context == null;
      if (!isParameterEntity) {
        entName = ExpatUtils.GetEntityName(context, reader.StrTable);
        if (!reader.ExternalGeneral) {
          if (reader.ContentHandler != null)
            // entName == null should not happen
            reader.ContentHandler.SkippedEntity(entName == null ? String.Empty : entName);
          return false;
        }
      }
      else {
        if (sysId == null) {  // use foreign DTD
          entName = KdsConsts.XmlDtdName;
          if (reader.parseData.HasDoctypeDecl)
            inSource = reader.parseData.ForeignDtdSource;
          else {
            inSource = reader.ResolveForeignDtd(null);
            reader.parseData.ForeignDtdSource = inSource;
          }
          if (inSource == null)
            return false;
        }  // don't have name for parameter entity, must look it up
        else {
          ParEntDeclEntry entry = reader.ParamEntityDecls.GetEntry(pubId, sysId);
          if (entry.Exists)
            entName = reader.ParamEntityDecls.GetName(entry);
        }
        if (entName != null) {
          if (!reader.ExternalParameter) {
            if (reader.ContentHandler != null)
              reader.ContentHandler.SkippedEntity(entName);
            return false;
          }
        }
        else {
          // Expat should not report an entity here if it hasn't read it's
          // declaration, since it would not know it's external id then
          string msg = KdsConsts.GetString(KdsRsId.UnknownEntity);
          msg = String.Format(msg, pubId, sysId);
          reader.CallErrorHandler(ErrorLevel.Error, msg);
          return false;
        }
      }

      // if we got that far, then we don't want to skip the entity
      ILexicalHandler lexHandler = reader.LexicalHandler;
      if (lexHandler != null) {
        // A foreign DTD without DocType declaration would not trigger a
        // StartDocTypeDecl call-back from Expat, so we call StartDtd here.
        if (sysId == null && !reader.parseData.HasDoctypeDecl)
          lexHandler.StartDtd(reader.ForeignDoctypeName,
                              inSource.PublicId,
                              inSource.SystemId);
        lexHandler.StartEntity(entName);
      }

      // input source not yet resolved, except for foreign DTD (sysId == null)
      if (inSource == null) {
        Exception error;
        string msg;
        // we are not using the baseUri parameter - we already have a copy in this instance
        inSource = reader.ResolveExternalEntity(
          entName, pubId, this.BaseUri, sysId, out msg, out error);
        if (inSource == null) {
          if (error == null)
            reader.CallErrorHandler(ErrorLevel.Error, msg);
          else
            reader.CallErrorHandler(ErrorLevel.Error, msg, error);
          return false;
        }
      }

      bool sourceOwned = false;
      InputSource<Stream> streamSource = inSource as InputSource<Stream>;
      if (streamSource != null) {
        read = new StreamBufferReader(streamSource.Source).Read;
        sourceOwned = streamSource is ExpatStreamSource;
      }
      else {
        InputSource<TextReader> readerSource = inSource as InputSource<TextReader>;
        if (readerSource != null)
          read = new TextBufferReader(readerSource.Source).Read;
        else {
          string msg = KdsConsts.GetString(KdsRsId.InvalidInputSource);
          reader.CallErrorHandler(ErrorLevel.Error, msg);
          return false;
        }
      }
      encoding = inSource.Encoding;
      Init(entName, isParameterEntity, inSource, streamSource != null, sourceOwned);
      return true;
    }

    // this is called *after* the parser's EntityParseContext has been popped
    protected override void Finish()
    {
      ExpatReader reader = Parser.UserData;
      try {
        ILexicalHandler lexHandler = reader.LexicalHandler;
        if (lexHandler != null) {
          lexHandler.EndEntity(Name);
          /* a foreign DTD without DocType declaration would not trigger
         * an EndDocTypeDecl call-back from Expat, so we call EndDtd here
         */
          if (reader.parseData.ForeignDtdSource != null) {
            if (reader.parseData.HasDoctypeDecl)
              lexHandler.EndDtd();
            reader.parseData.ForeignDtdSource = null;
          }
        }
      }
      finally {
        base.Finish();
      }
    }

    protected override void Reset()
    {
      base.Reset();
      name = null;
      isParameterEntity = false;
      version = null;
      publicId = null;
      systemId = null;
      baseUri = null;
      // stream/reader could be re-used in root context
      // so let's not close or clear them
      if (Parent != null) {
        stream = null;
        reader = null;
      }
    }

    public Stream Stream
    {
      get { return stream; }
    }

    public TextReader Reader
    {
      get { return reader; }
    }

    public string PublicId
    {
      get { return publicId; }
    }

    public string SystemId
    {
      get { return systemId; }
    }

    public string Name
    {
      get { return name; }
    }

    public bool IsParameterEntity
    {
      get { return isParameterEntity; }
    }

    public string Encoding
    {
      get { return encoding; }
      set { encoding = value; }
    }

    public Uri BaseUri
    {
      get { return baseUri; }
      set { baseUri = value; }
    }

    public string Version
    {
      get { return version; }
      set { version = value; }
    }
  }

  /**<summary>Class for internal use in <see cref="ExpatReader"/>.
   * Represents Expat parser.</summary>
   */
  internal class SaxExpatParser: ExpatParser<SaxExpatParser, SaxEntityContext, ExpatReader>
  {
    public SaxExpatParser(string encoding, bool namespaces, ExpatReader userData)
      : base(encoding, namespaces, userData) { }
  }

}

www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.