xml: Port SAX 2.0.
authorJean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
Fri, 19 Sep 2014 13:44:15 +0000 (09:44 -0400)
committerJean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
Mon, 27 Oct 2014 17:50:01 +0000 (13:50 -0400)
This is a port of SAX 2.0.1 (http://www.saxproject.org) in Nit. The
following is **not** included in this port:

* Support for SAX1.
* Exceptions.
* Factories (requires run-time loading).

Signed-off-by: Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>

21 files changed:
lib/sax/attributes.nit [new file with mode: 0644]
lib/sax/content_handler.nit [new file with mode: 0644]
lib/sax/dtd_handler.nit [new file with mode: 0644]
lib/sax/entity_resolver.nit [new file with mode: 0644]
lib/sax/error_handler.nit [new file with mode: 0644]
lib/sax/ext/decl_handler.nit [new file with mode: 0644]
lib/sax/ext/ext.nit [new file with mode: 0644]
lib/sax/ext/lexical_handler.nit [new file with mode: 0644]
lib/sax/helpers/attributes_impl.nit [new file with mode: 0644]
lib/sax/helpers/helpers.nit [new file with mode: 0644]
lib/sax/helpers/namespace_support.nit [new file with mode: 0644]
lib/sax/helpers/sax_locator_impl.nit [new file with mode: 0644]
lib/sax/helpers/test_attributes_impl.nit [new file with mode: 0644]
lib/sax/helpers/test_namespace_support.nit [new file with mode: 0644]
lib/sax/helpers/xml_filter_impl.nit [new file with mode: 0644]
lib/sax/input_source.nit [new file with mode: 0644]
lib/sax/sax.nit [new file with mode: 0644]
lib/sax/sax_locator.nit [new file with mode: 0644]
lib/sax/sax_parse_exception.nit [new file with mode: 0644]
lib/sax/xml_filter.nit [new file with mode: 0644]
lib/sax/xml_reader.nit [new file with mode: 0644]

diff --git a/lib/sax/attributes.nit b/lib/sax/attributes.nit
new file mode 100644 (file)
index 0000000..9d0592c
--- /dev/null
@@ -0,0 +1,210 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Interface for a list of XML attributes.
+module sax::attributes
+
+# Interface for a list of XML attributes.
+#
+# This interface allows access to a list of attributes in
+# three different ways:
+#
+# * by attribute index;
+# * by Namespace-qualified name; or
+# * by qualified (prefixed) name.
+#
+# The list will not contain attributes that were declared
+# `#IMPLIED` but not specified in the start tag. It will also not
+# contain attributes used as Namespace declarations (`xmlns*`) unless
+# the `http://xml.org/sax/features/namespace-prefixes`
+# feature is set to `true` (it is `false` by default).
+# Because SAX2 conforms to the "Namespaces in XML" specification,
+# it does not give namespace declaration attributes a namespace URI.
+# Some other W3C specifications are in conflict with that, expecting
+# these declarations to be in a namespace.
+# Handler code may need to resolve that conflict.
+#
+# If the namespace-prefixes feature (see above) is `false`,
+# access by qualified name may not be available; if the
+# `http://xml.org/sax/features/namespaces`
+# feature is `false`, access by Namespace-qualified names
+# may not be available.
+#
+# The order of attributes in the list is unspecified, and will
+# vary from implementation to implementation.
+#
+# Note: The original documentation comes from [SAX 2.0](http://www.saxproject.org).
+#
+# SEE: `sax::helpers::AttributesImpl`
+#
+# SEE: `sax::ext::DeclHandler.attribute_decl`
+interface Attributes
+
+       # Return the number of attributes in the list.
+       fun length: Int is abstract
+
+       # Look up an attribute's Namespace URI by index.
+       #
+       # Parameters:
+       #
+       # * `index`: attribute index.
+       #
+       # Returns:
+       #
+       # The Namespace URI, or the empty string if none
+       # is available, or `null` if the index is out of
+       # range.
+       #
+       # SEE: `length`
+       fun uri(index: Int): nullable String is abstract
+
+       # Look up an attribute's local name by index.
+       #
+       # Parameters:
+       #
+       # * `index`: attribute index.
+       #
+       # Returns:
+       #
+       # The local name, or the empty string if Namespace
+       # processing is not being performed, or `null`
+       # if the index is out of range.
+       #
+       # SEE: `length`
+       fun local_name(index: Int): nullable String is abstract
+
+       # Look up an attribute's XML 1.0 qualified name by index.
+       #
+       # Parameters:
+       #
+       # * `index`: attribute index.
+       #
+       # Returns:
+       #
+       # The XML 1.0 qualified name, or the empty string
+       # if none is available, or `null` if the index
+       # is out of range.
+       #
+       # SEE: `length`
+       fun qname(index: Int): nullable String is abstract
+
+       # Look up an attribute's type by index.
+       #
+       # The attribute type is one of the strings `CDATA`, `ID`,
+       # `IDREF`, `IDREFS`, `NMTOKEN`, `NMTOKENS`, `ENTITY`, `ENTITIES`,
+       # or `NOTATION` (always in upper case).
+       #
+       # If the parser has not read a declaration for the attribute,
+       # or if the parser does not report attribute types, then it must
+       # return the value `CDATA` as stated in the XML 1.0 Recommentation
+       # (clause 3.3.3, "Attribute-Value Normalization").
+       #
+       # For an enumerated attribute that is not a notation, the
+       # parser will report the type as `NMTOKEN`.
+       #
+       # Parameters:
+       #
+       # * `index: Int`: attribute index.
+       # * `index: String`: XML 1.0 qualified (prefixed) name.
+       #
+       # Returns:
+       #
+       # The attribute's type as a string, or `null` if the specified
+       # attribute is not in the list or if qualified names
+       # are not available.
+       #
+       # SEE: `length`
+       fun type_of(index: Object): nullable String is abstract
+
+       # Look up an attribute's value by index.
+       #
+       # If the attribute value is a list of tokens (`IDREFS`,
+       # `ENTITIES`, or `NMTOKENS`), the tokens will be concatenated
+       # into a single string with each token separated by a
+       # single space.
+       #
+       # Parameters:
+       #
+       # * `index: Int`: attribute index.
+       # * `index: String`: XML 1.0 qualified (prefixed) name.
+       #
+       # Returns:
+       #
+       # The attribute's value as a string, or `null` if the specified
+       # attribute is not in the list or if qualified names
+       # are not available.
+       #
+       # SEE: `length`
+       fun value_of(index: Object): nullable String is abstract
+
+       # Look up the index of an attribute by Namespace name.
+       #
+       # Parameters:
+       #
+       # * `uri`: Namespace URI, or the empty string if
+       # the name has no Namespace URI.
+       # * `local_name`: attribute's local name.
+       #
+       # Returns:
+       #
+       # The index of the attribute, or -1 if it does not
+       # appear in the list.
+       fun index_ns(uri: String, local_name: String): Int is abstract
+
+       # Look up the index of an attribute by XML 1.0 qualified name.
+       #
+       # Parameters:
+       #
+       # * `qname`: XML 1.0 qualified (prefixed) name.
+       #
+       # Returns:
+       #
+       # The index of the attribute, or -1 if it does not
+       # appear in the list.
+       fun index_of(qname: String): Int is abstract
+
+       # Look up an attribute's type by Namespace name.
+       #
+       # See `type_of` for a description
+       # of the possible types.
+       #
+       # Parameters:
+       #
+       # * `uri`: Namespace URI, or the empty string if
+       # the name has no Namespace URI.
+       #
+       # * `local_name`: attribute's local name.
+       #
+       # Returns:
+       #
+       # The attribute type as a string, or `null` if the
+       # attribute is not in the list or if Namespace
+       # processing is not being performed.
+       fun type_ns(uri: String, local_name: String): nullable String is abstract
+
+       # Look up an attribute's value by Namespace name.
+       #
+       # See `value_of` for a description
+       # of the possible values.
+       #
+       # Parameters:
+       #
+       # * `uri`: Namespace URI, or the empty string if
+       # the name has no Namespace URI.
+       #
+       # * `local_name`: attribute's local name.
+       #
+       # Returns:
+       #
+       # The attribute value as a string, or `null` if the
+       # attribute is not in the list or if Namespace
+       # processing is not being performed.
+       fun value_ns(uri: String, local_name: String): nullable String is abstract
+end
diff --git a/lib/sax/content_handler.nit b/lib/sax/content_handler.nit
new file mode 100644 (file)
index 0000000..e47c687
--- /dev/null
@@ -0,0 +1,308 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Receives notification of the logical content of a document.
+module sax::content_handler
+
+import attributes
+import sax_locator
+
+# Receives notification of the logical content of a document.
+#
+# This is the main interface that most SAX applications
+# implement: if the application needs to be informed of basic parsing
+# events, it implements this interface and registers an instance with
+# the SAX parser using the `sax::XMLReader.content_handler`
+# attribute. The parser uses the instance to report
+# basic document-related events like the start and end of elements
+# and character data.
+#
+# The order of events in this interface is very important, and
+# mirrors the order of information in the document itself. For
+# example, all of an element's content (character data, processing
+# instructions, and/or subelements) will appear, in order, between
+# the startElement event and the corresponding endElement event.
+#
+# Note: The original documentation comes from [SAX 2.0](http://www.saxproject.org).
+#
+# SEE: `sax::XMLReader`
+#
+# SEE: `sax::DTDHandler`
+#
+# SEE: `sax::ErrorHandler`
+abstract class ContentHandler
+
+       # Receive an object for locating the origin of SAX document events.
+       #
+       # SAX parsers are strongly encouraged (though not absolutely
+       # required) to supply a locator: if it does so, it must supply
+       # the locator to the application by invoking this method before
+       # invoking any of the other methods in the ContentHandler
+       # interface.
+       #
+       # The locator allows the application to determine the end
+       # position of any document-related event, even if the parser is
+       # not reporting an error. Typically, the application will
+       # use this information for reporting its own errors (such as
+       # character content that does not match an application's
+       # business rules). The information returned by the locator
+       # is probably not sufficient for use with a search engine.
+       #
+       # Note that the locator will return correct information only
+       # during the invocation of the events in this interface. The
+       # application should not attempt to use it at any other time.
+       #
+       # Parameter:
+       #
+       # * `locator`: object that can return the location of
+       # any SAX document event.
+       #
+       # SEE: `sax::SAXLocator`
+       fun document_locator=(locator: SAXLocator) do end
+
+
+       # Receive notification of the beginning of a document.
+       #
+       # The SAX parser will invoke this method only once, before any
+       # other event callbacks (except for `document_locator`).
+       #
+       # SEE: `end_document`
+       fun start_document do end
+
+
+       # Receive notification of the end of a document.
+       #
+       # The SAX parser will invoke this method only once, and it will
+       # be the last method invoked during the parse. The parser shall
+       # not invoke this method until it has either abandoned parsing
+       # (because of an unrecoverable error) or reached the end of
+       # input.
+       #
+       # SEE: `start_document`
+       fun end_document do end
+
+
+       # Begin the scope of a prefix-URI Namespace mapping.
+       #
+       # The information from this event is not necessary for
+       # normal Namespace processing: the SAX XML reader will
+       # automatically replace prefixes for element and attribute
+       # names when the `http://xml.org/sax/features/namespaces`
+       # feature is `true` (the default).
+       #
+       # There are cases, however, when applications need to
+       # use prefixes in character data or in attribute values,
+       # where they cannot safely be expanded automatically; the
+       # `start/end_prefix_mapping` event supplies the information
+       # to the application to expand prefixes in those contexts
+       # itself, if necessary.
+       #
+       # Note that `start/end_prefix_mapping` events are not
+       # guaranteed to be properly nested relative to each other:
+       # all `start/end_prefix_mapping` events will occur immediately before the
+       # corresponding `start_element` event,
+       # and all `end_prefix_mapping`
+       # events will occur immediately after the corresponding
+       # `end_element` event, but their order is not otherwise
+       # guaranteed.
+       #
+       # There should never be `start/end_prefix_mapping` events for the
+       # `xml` prefix, since it is predeclared and immutable.
+       #
+       # Parameters:
+       #
+       # * `prefix`: Namespace prefix being declared.
+       #An empty string is used for the default element namespace,
+       #which has no prefix.
+       # * `uri`: Namespace URI the prefix is mapped to.
+       #
+       # SEE: `end_prefix_mapping`
+       # SEE: `start_element`
+       fun start_prefix_mapping(prefix: String, uri: String) do end
+
+
+       # End the scope of a prefix-URI mapping.
+       #
+       # See `start_prefix_mapping` for
+       # details. These events will always occur immediately after the
+       # corresponding `end_prefix_mapping` event, but the order of
+       # `end_prefix_mapping` events is not otherwise
+       # guaranteed.
+       #
+       # Parameter:
+       #
+       # * `prefix`: prefix that was being mapping.
+       # This is the empty string when a default mapping scope ends.
+       #
+       # SEE: `start_prefix_mapping`
+       # SEE: `end_element`
+       fun end_prefix_mapping(prefix: String) do end
+
+       # Receive notification of the beginning of an element.
+       #
+       # The Parser will invoke this method at the beginning of every
+       # element in the XML document; there will be a corresponding
+       # `end_element` event for every startElement event
+       # (even when the element is empty). All of the element's content will be
+       # reported, in order, before the corresponding `end_element`
+       # event.
+       #
+       # This event allows up to three name components for each
+       # element:
+       #
+       # 1. the Namespace URI;
+       # 2. the local name; and
+       # 3. the qualified (prefixed) name.
+       #
+       # Any or all of these may be provided, depending on the
+       # values of the `http://xml.org/sax/features/namespaces`
+       # and the `http://xml.org/sax/features/namespace-prefixes`
+       # properties:
+       #
+       # * the Namespace URI and local name are required when
+       # the namespaces property is `true` (the default), and are
+       # optional when the namespaces property is `false` (if one is
+       # specified, both must be);
+       # * the qualified name is required when the namespace-prefixes property
+       # is `true`, and is optional when the namespace-prefixes property
+       # is `false` (the default).
+       #
+       # Note that the attribute list provided will contain only
+       # attributes with explicit values (specified or defaulted):
+       # `#IMPLIED` attributes will be omitted. The attribute list
+       # will contain attributes used for Namespace declarations
+       # (`xmlns*` attributes) only if the
+       # `http://xml.org/sax/features/namespace-prefixes`
+       # property is true (it is `false` by default, and support for a
+       # `true` value is optional).
+       #
+       # Parameters:
+       #
+       # * `uri`: Namespace URI, or the empty string if the
+       # element has no Namespace URI or if Namespace
+       # processing is not being performed.
+       # * `localName`: local name (without prefix), or the
+       # empty string if Namespace processing is not being
+       # performed.
+       # * `qname`: The qualified XML 1.0 name (with prefix), or the
+       # empty string if qualified names are not available.
+       # * `atts`: attributes attached to the element.
+       #
+       # SEE: `end_element`
+       # SEE: `sax::Attributes`
+       fun start_element(uri: String, local_name: String, qname: String,
+                       atts: Attributes) do end
+
+       # Receive notification of the end of an element.
+       #
+       # The SAX parser will invoke this method at the end of every
+       # element in the XML document; there will be a corresponding
+       # `start_element` event for every `end_element`
+       # event (even when the element is empty).
+       #
+       # For information on the names, see `start_element`.
+       #
+       # Parameters:
+       #
+       # * `uri`: Namespace URI, or the empty string if the
+       # element has no Namespace URI or if Namespace
+       # processing is not being performed.
+       # * `localName`: local name (without prefix), or the
+       # empty string if Namespace processing is not being
+       # performed.
+       # * `qname`: The qualified XML 1.0 name (with prefix), or the
+       # empty string if qualified names are not available.
+       fun end_element(uri: String, local_name: String, qname: String) do end
+
+       # Receive notification of character data.
+       #
+       # The Parser will call this method to report each chunk of
+       # character data. SAX parsers may return all contiguous character
+       # data in a single chunk, or they may split it into several
+       # chunks; however, all of the characters in any single event
+       # must come from the same external entity so that the `SAXLocator`
+       # provides useful information.
+       #
+       # Note that some parsers will report whitespace in element
+       # content using the `ignorable_whitespace`
+       # method rather than this one (validating parsers *must* do so).
+       #
+       # Parameter:
+       #
+       # * `str`: characters from the XML document.
+       #
+       # SEE: `ignorable_whitespace`
+       # SEE: `sax::SAXLocator`
+       fun characters(str: String) do end
+
+       # Receive notification of ignorable whitespace in element content.
+       #
+       # Validating Parsers must use this method to report each chunk
+       # of whitespace in element content (see the W3C XML 1.0 recommendation,
+       # section 2.10): non-validating parsers may also use this method
+       # if they are capable of parsing and using content models.
+       #
+       # SAX parsers may return all contiguous whitespace in a single
+       # chunk, or they may split it into several chunks; however, all of
+       # the characters in any single event must come from the same
+       # external entity, so that the `SAXLocator` provides useful
+       # information.
+       #
+       # Parameter:
+       #
+       # * `str`: characters from the XML document.
+       #
+       # SEE: `characters`
+       fun ignorable_whitespace(str: String) do end
+
+       # Receive notification of a processing instruction.
+       #
+       # The Parser will invoke this method once for each processing
+       # instruction found: note that processing instructions may occur
+       # before or after the main document element.
+       #
+       # A SAX parser must never report an XML declaration (XML 1.0,
+       # section 2.8) or a text declaration (XML 1.0, section 4.3.1)
+       # using this method.
+       #
+       # Parameters:
+       #
+       # * `target`: processing instruction target.
+       # * `data`: processing instruction data, or `null` if
+       # none was supplied. The data does not include any
+       # whitespace separating it from the target.
+       fun processing_instruction(target: String, data: nullable String) do end
+
+       # Receive notification of a skipped entity.
+       #
+       # This is not called for entity references within markup constructs
+       # such as element start tags or markup declarations. (The XML
+       # recommendation requires reporting skipped external entities.
+       # SAX also reports internal entity expansion/non-expansion, except
+       # within markup constructs.)
+       #
+       # The Parser will invoke this method each time the entity is
+       # skipped. Non-validating processors may skip entities if they
+       # have not seen the declarations (because, for example, the
+       # entity was declared in an external DTD subset). All processors
+       # may skip external entities, depending on the values of the
+       # `http://xml.org/sax/features/external-general-entities`
+       # and the
+       # `http://xml.org/sax/features/external-parameter-entities`
+       # properties.
+       #
+       # Parameter:
+       #
+       # * `name`: The name of the skipped entity. If it is a
+       # parameter entity, the name will begin with `%`, and if
+       # it is the external DTD subset, it will be the string
+       # `[dtd]`.
+       fun skipped_entity(name: String) do end
+end
diff --git a/lib/sax/dtd_handler.nit b/lib/sax/dtd_handler.nit
new file mode 100644 (file)
index 0000000..a89126c
--- /dev/null
@@ -0,0 +1,92 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Receives notification of basic DTD-related events.
+module sax::dtd_handler
+
+# Receives notification of basic DTD-related events.
+#
+# If a SAX application needs information about notations and
+# unparsed entities, then the application implements this
+# interface and registers an instance with the SAX parser using
+# the parser's `dtd_handler` property. The parser uses the
+# instance to report notation and unparsed entity declarations to
+# the application.
+#
+# Note that this interface includes only those DTD events that
+# the XML recommendation *requires* processors to report:
+# notation and unparsed entity declarations.
+#
+# The SAX parser may report these events in any order, regardless
+# of the order in which the notations and unparsed entities were
+# declared; however, all DTD events must be reported after the
+# document handler's `start_document` event, and before the first
+# `start_element` event.
+# (If the `sax::ext::LexicalHandler` is
+# used, these events must also be reported before the `end_dtd` event.)
+#
+# It is up to the application to store the information for
+# future use (perhaps in a hash table or object tree).
+# If the application encounters attributes of type `NOTATION`,
+# `ENTITY`, or `ENTITIES`, it can use the information that it
+# obtained through this interface to find the entity and/or
+# notation corresponding with the attribute value.
+#
+# Note: The original documentation comes from [SAX 2.0](http://www.saxproject.org).
+#
+# SEE: `sax::XMLReader.dtd_handler`
+abstract class DTDHandler
+
+       # Receive notification of a notation declaration event.
+       #
+       # It is up to the application to record the notation for later
+       # reference, if necessary;
+       # notations may appear as attribute values and in unparsed entity
+       # declarations, and are sometime used with processing instruction
+       # target names.
+       #
+       # At least one of `public_id` and `system_id` must be non-null.
+       # If a system identifier is present, and it is a URL, the SAX
+       # parser must resolve it fully before passing it to the
+       # application through this event.
+       #
+       # There is no guarantee that the notation declaration will be
+       # reported before any unparsed entities that use it.
+       #
+       # Parameters:
+       #
+       # * `name`: notation name.
+       # * `public_id`: notation's public identifier, or null if none was given.
+       # * `system_id`: notation's system identifier, or null if none was given.
+       #
+       # SEE: `sax::Attributes`
+       fun notation_decl(name: String, public_id: String, system_id: String) do end
+
+       # Receive notification of an unparsed entity declaration event.
+       #
+       # Note that the notation name corresponds to a notation
+       # reported by the `notation_decl` event.
+       # It is up to the application to record the entity for later
+       # reference, if necessary;
+       # unparsed entities may appear as attribute values.
+       #
+       # If the system identifier is a URL, the parser must resolve it
+       # fully before passing it to the application.
+       #
+       # Parameters:
+       #
+       # * `name`: unparsed entity's name.
+       # * `public_id`: entity's public identifier, or null if none was given.
+       # * `system_id`: entity's system identifier, or null if none was given.
+       #
+       # SEE: `sax::Attributes`
+       fun unparsed_entity_decl(name: String, public_id: String,
+                       system_id: String) do end
+end
diff --git a/lib/sax/entity_resolver.nit b/lib/sax/entity_resolver.nit
new file mode 100644 (file)
index 0000000..3709056
--- /dev/null
@@ -0,0 +1,85 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Basic interface for resolving entities.
+module sax::entity_resolver
+
+import input_source
+
+# Basic interface for resolving entities.
+#
+# If a SAX application needs to implement customized handling
+# for external entities, it must implement this interface and
+# register an instance with the SAX driver using the
+# `sax::XMLReader.entity_resolver` property.
+#
+# The XML reader will then allow the application to intercept any
+# external entities (including the external DTD subset and external
+# parameter entities, if any) before including them.
+#
+# Many SAX applications will not need to implement this interface,
+# but it will be especially useful for applications that build
+# XML documents from databases or other specialised input sources,
+# or for applications that use URI types other than URLs.
+#
+# The application can also use this interface to redirect system
+# identifiers to local URIs or to look up replacements in a catalog
+# (possibly by using the public identifier).
+#
+# Note: The original documentation comes from [SAX 2.0](http://www.saxproject.org).
+#
+# SEE: `sax::XMLReader.entity_resolver`
+#
+# SEE: `sax::InputSource`
+abstract class EntityResolver
+
+       # Allow the application to resolve external entities.
+       #
+       # The parser will call this method before opening any external
+       # entity except the top-level document entity. Such entities include
+       # the external DTD subset and external parameter entities referenced
+       # within the DTD (in either case, only if the parser reads external
+       # parameter entities), and external general entities referenced
+       # within the document element (if the parser reads external general
+       # entities). The application may request that the parser locate
+       # the entity itself, that it use an alternative URI, or that it
+       # use data provided by the application (as a character or byte
+       # input stream).
+       #
+       # Application writers can use this method to redirect external
+       # system identifiers to secure and/or local URIs, to look up
+       # public identifiers in a catalogue, or to read an entity from a
+       # database or other input source (including, for example, a dialog
+       # box). Neither XML nor SAX specifies a preferred policy for using
+       # public or system IDs to resolve resources. However, SAX specifies
+       # how to interpret any InputSource returned by this method, and that
+       # if none is returned, then the system ID will be dereferenced as
+       # a URL.
+       #
+       # If the system identifier is a URL, the SAX parser must
+       # resolve it fully before reporting it to the application.
+       #
+       # Parameters:
+       #
+       # * `public_id`: public identifier of the external entity
+       # being referenced, or `null` if none was supplied.
+       # * `system_id`: system identifier of the external entity
+       # being referenced.
+       #
+       # Returns:
+       #
+       # An `InputSource` object describing the new input source,
+       # or `null` to request that the parser open a regular
+       # URI connection to the system identifier.
+       fun resolve_entity(public_id: nullable String, system_id: nullable String):
+                       nullable InputSource do
+               return null
+       end
+end
diff --git a/lib/sax/error_handler.nit b/lib/sax/error_handler.nit
new file mode 100644 (file)
index 0000000..cbdc68a
--- /dev/null
@@ -0,0 +1,103 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Basic interface for SAX error handlers.
+module sax::error_handler
+
+import sax_parse_exception
+
+# Basic interface for SAX error handlers.
+#
+# If a SAX application needs to implement customized error
+# handling, it must implement this interface and then register an
+# instance with the XML reader using the `sax::XMLReader.error_handler
+# property. The parser will then report all errors and warnings
+# through this interface.
+#
+# **WARNING:** If an application does *not* register an `ErrorHandler`,
+# XML parsing errors will go unreported and bizarre behaviour may result.
+#
+# For XML processing errors, a SAX driver must use this interface
+# instead of throwing an exception: it is up to the application
+# to decide whether to throw an exception for different types of
+# errors and warnings. Note, however, that there is no requirement that
+# the parser continue to provide useful information after a call to
+# `fatal_error` (in other words, a SAX driver class
+# could catch an exception and report a `fatal_error`).
+#
+# Note: The original documentation comes from [SAX 2.0](http://www.saxproject.org).
+#
+# SEE: `sax::XMLReader.error_handler`
+abstract class ErrorHandler
+
+       # Receive notification of a warning.
+       #
+       # SAX parsers will use this method to report conditions that
+       # are not errors or fatal errors as defined by the XML 1.0
+       # recommendation. The default behaviour is to take no action.
+       #
+       # The SAX parser must continue to provide normal parsing events
+       # after invoking this method: it should still be possible for the
+       # application to process the document through to the end.
+       #
+       # Filters may use this method to report other, non-XML warnings
+       # as well.
+       #
+       # Parameter:
+       #
+       # * `exception`: warning information encapsulated in a SAX parse
+       # exception.
+       fun warning(exception: SAXParseException) do end
+
+       # Receive notification of a recoverable error.
+       #
+       # This corresponds to the definition of "error" in section 1.2
+       # of the W3C XML 1.0 Recommendation. For example, a validating
+       # parser would use this callback to report the violation of a
+       # validity constraint. The default behaviour is to take no
+       # action.
+       #
+       # The SAX parser must continue to provide normal parsing events
+       # after invoking this method: it should still be possible for the
+       # application to process the document through to the end. If the
+       # application cannot do so, then the parser should report a fatal
+       # error even if the XML 1.0 recommendation does not require it to
+       # do so.
+       #
+       # Filters may use this method to report other, non-XML errors
+       # as well.
+       #
+       # Parameter:
+       #
+       # * `exception`: error information encapsulated in a SAX parse
+       # exception.
+       fun error(exception: SAXParseException) do end
+
+       # Receive notification of a non-recoverable error.
+       #
+       # This corresponds to the definition of "fatal error" in
+       # section 1.2 of the W3C XML 1.0 Recommendation. For example, a
+       # parser would use this callback to report the violation of a
+       # well-formedness constraint.
+       #
+       # The application must assume that the document is unusable
+       # after the parser has invoked this method, and should continue
+       # (if at all) only for the sake of collecting additional error
+       # messages: in fact, SAX parsers are free to stop reporting any
+       # other events once this method has been invoked.
+       #
+       # Parameter:
+       #
+       # * `exception`: error information encapsulated in a SAX parse
+       # exception.
+       fun fatal_error(exception: SAXParseException) do
+               exception.throw
+       end
+end
diff --git a/lib/sax/ext/decl_handler.nit b/lib/sax/ext/decl_handler.nit
new file mode 100644 (file)
index 0000000..8cf6974
--- /dev/null
@@ -0,0 +1,113 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# SAX2 extension handler for DTD declaration events.
+module sax::ext::decl_handler
+
+# SAX2 extension handler for DTD declaration events.
+#
+# This is an optional extension handler for SAX2 to provide more
+# complete information about DTD declarations in an XML document.
+# XML readers are not required to recognize this handler, and it
+# is not part of core-only SAX2 distributions.
+#
+# Note that data-related DTD declarations (unparsed entities and
+# notations) are already reported through the `DTDHandler` interface.
+#
+# If you are using the declaration handler together with a lexical
+# handler, all of the events will occur between the `start_dtd` and the
+# `end_dtd` events.
+#
+# To set the `DeclHandler` for an XML reader, use the
+# `set_property` method with the property name
+# `http://xml.org/sax/properties/declaration-handler`
+# and an object implementing this interface (or `null`) as the value.
+abstract class DeclHandler
+       # Report an element type declaration.
+       #
+       # The content model will consist of the string `EMPTY`, the
+       # string `ANY`, or a parenthesised group, optionally followed
+       # by an occurrence indicator. The model will be normalized so
+       # that all parameter entities are fully resolved and all whitespace
+       # is removed,and will include the enclosing parentheses. Other
+       # normalization (such as removing redundant parentheses or
+       # simplifying occurrence indicators) is at the discretion of the
+       # parser.
+       #
+       # Parameters:
+       #
+       # * `name`: element type name.
+       # * `model`: content model as a normalized string.
+       fun element_decl(name: String, model: String) do end
+
+       # Report an attribute type declaration.
+       #
+       # Only the effective (first) declaration for an attribute will
+       # be reported. The type will be one of the strings `CDATA`,
+       # `ID`, `IDREF`, `IDREFS`, `NMTOKEN`, `NMTOKENS`, `ENTITY`,
+       # `ENTITIES`, a parenthesized token group with
+       # the separator `|` and all whitespace removed, or the word
+       # `NOTATION` followed by a space followed by a parenthesized
+       # token group with all whitespace removed.
+       #
+       # The value will be the value as reported to applications,
+       # appropriately normalized and with entity and character
+       # references expanded.
+       #
+       # Parameters:
+       #
+       # * `element_name`: name of the associated element.
+       # * `attribute_name`: name of the attribute.
+       # * `attribute_type`: string representing the attribute type.
+       # * `mode`: string representing the attribute defaulting mode (`#IMPLIED`,
+       # `#REQUIRED`, or `#FIXED`) or `null` if none of these applies.
+       # * `value`: string representing the attribute's default value,
+       # or `null` if there is none.
+       fun attribute_decl(element_name: String,
+                       attribute_name: String,
+                       attribute_type: String,
+                       mode: nullable String,
+                       value: nullable String) do end
+
+       # Report an internal entity declaration.
+       #
+       # Only the effective (first) declaration for each entity
+       # will be reported. All parameter entities in the value
+       # will be expanded, but general entities will not.
+       #
+       # Parameters:
+       #
+       # * `name`: name of the entity. If it is a parameter entity, the name will
+       # begin with `%`.
+       # * `value`: replacement text of the entity.
+       #
+       # SEE: `external_entity_decl`
+       #
+       # SEE: `sax::DTDHandler.unparsed_entity_decl`
+       fun internal_entity_decl(name: String, value: String) do end
+
+       # Report a parsed external entity declaration.
+       #
+       # Only the effective (first) declaration for each entity
+       # will be reported.
+       #
+       # Parameters:
+       #
+       # * `name`: name of the entity. If it is a parameter entity, the name will
+       # begin with `%`.
+       # * `public_id`: declared public identifier of the entity, or `null` if
+       # none was declared.
+       # * `system_id`: declared system identifier of the entity.
+       #
+       # SEE: `internal_entity_decl`
+       #
+       # SEE: `sax::DTDHandler.unparsed_entity_decl`
+       fun external_entity_decl(name: String, value: String) do end
+end
diff --git a/lib/sax/ext/ext.nit b/lib/sax/ext/ext.nit
new file mode 100644 (file)
index 0000000..a016b30
--- /dev/null
@@ -0,0 +1,53 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Interfaces to optional SAX2 handlers.
+#
+# See [http://www.saxproject.org](http://www.saxproject.org)
+# for more information about SAX.
+#
+# The package is independent of the SAX2 core, though the functionality
+# exposed generally needs to be implemented within a parser.
+# That independence has several consequences:
+#
+# * SAX2 drivers are *not* required to recognize these handlers,
+# and you cannot assume that the class files will be present in every SAX2
+# installation.
+#
+# * This package may be updated independently of SAX2 (i.e. new
+# handlers may be added without updating SAX2 itself).
+#
+# * The handlers are not implemented by the SAX2
+# `org.xml.sax.helpers.XMLFilterImpl` class.
+# You can subclass these if you need such behaviour.
+#
+# * The handlers need to be registered differently than regular SAX2
+# handlers.
+#
+# This package, SAX2-ext, is a standardized extension to SAX2. It is
+# designed both to allow SAX parsers to pass certain types of information
+# to applications, and to serve as a simple model for other SAX2 parser
+# extension packages. Not all such extension packages should need to
+# be recognized directly by parsers, however.
+# As an example, most schema systems can be cleanly layered on top
+# of parsers supporting the standardized SAX2 interfaces.
+#
+# Note: this package alone does add any
+# functionality; it simply provides optional interfaces for SAX2 drivers
+# to use. You must use a SAX2 driver that recognizes these interfaces if
+# you actually want to have access to lexical and declaration
+# information.
+#
+# Note: The original documentation comes from [SAX 2.0](http://www.saxproject.org).
+module sax::ext
+
+import sax
+import decl_handler
+import lexical_handler
diff --git a/lib/sax/ext/lexical_handler.nit b/lib/sax/ext/lexical_handler.nit
new file mode 100644 (file)
index 0000000..a207f60
--- /dev/null
@@ -0,0 +1,163 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# SAX2 extension handler for lexical events.
+module sax::ext::lexical_handler
+
+# SAX2 extension handler for lexical events.
+#
+#
+# This is an optional extension handler for SAX2 to provide
+# lexical information about an XML document, such as comments
+# and `CDATA` section boundaries.
+# XML readers are not required to recognize this handler, and it
+# is not part of core-only SAX2 distributions.
+#
+# The events in the lexical handler apply to the entire document,
+# not just to the document element, and all lexical handler events
+# must appear between the content handler's startDocument and
+# endDocument events.
+#
+# To set the LexicalHandler for an XML reader, use the
+# `setProperty` method
+# with the property name
+# `http://xml.org/sax/properties/lexical-handler`
+# and an object implementing this interface (or `null`) as the value.
+#
+# Note: The original documentation comes from [SAX 2.0](http://www.saxproject.org).
+abstract class LexicalHandler
+
+       # Report the start of DTD declarations, if any.
+       #
+       # This method is intended to report the beginning of the
+       # `DOCTYPE` declaration; if the document has no `DOCTYPE` declaration,
+       # this method will not be invoked.
+       #
+       # All declarations reported through `DTDHandler` or `DeclHandler` events
+       # must appear between the `start_dtd` and `end_dtd` events.
+       # Declarations are assumed to belong to the internal DTD subset
+       # unless they appear between `start_entity` and `end_entity` events.
+       # Comments and processing instructions from the DTD should also be reported
+       # between the `start_dtd` and `end_dtd` events, in their original
+       # order of (logical) occurrence; they are not required to
+       # appear in their correct locations relative to `DTDHandler`
+       # or `DeclHandler` events, however.
+       #
+       # Note that the `start_dtd`/`end_dtd` events will appear within
+       # the `start_document` and `end_document` events from `ContentHandler` and
+       # before the first `start_element` event.
+       #
+       # Parameters:
+       #
+       # * `name`: document type name.
+       # * `public_id`: declared public identifier for the
+       # external DTD subset, or `null` if none was declared.
+       # * `system_id`: declared system identifier for the
+       # external DTD subset, or `null` if none was declared.
+       # (Note that this is not resolved against the document
+       # base URI.)
+       #
+       # SEE: `end_dtd`
+       #
+       # SEE: `start_entity`
+       fun start_dtd(name: String, public_id: nullable String,
+                       system_id: nullable String) do end
+
+       # Report the end of DTD declarations.
+       #
+       # This method is intended to report the end of the
+       # `DOCTYPE` declaration; if the document has no `DOCTYPE` declaration,
+       # this method will not be invoked.
+       #
+       # SEE: `start_dtd`
+       fun end_dtd do end
+
+       # Report the beginning of some internal and external XML entities.
+       #
+       # The reporting of parameter entities (including
+       # the external DTD subset) is optional, and SAX2 drivers that
+       # report `LexicalHandler` events may not implement it; you can use the
+       # `http://xml.org/sax/features/lexical-handler/parameter-entities`
+       # feature to query or control the reporting of parameter entities.
+       #
+       # General entities are reported with their regular names,
+       # parameter entities have `%` prepended to their names, and
+       # the external DTD subset has the pseudo-entity name `[dtd]`.
+       #
+       # When a SAX2 driver is providing these events, all other
+       # events must be properly nested within start/end entity
+       # events. There is no additional requirement that events from
+       # `DeclHandler` or `org.xml.sax.DTDHandler DTDHandler` be properly ordered.
+       #
+       # Note that skipped entities will be reported through the
+       # `skippedEntity` event, which is part of the `ContentHandler` interface.
+       #
+       # Because of the streaming event model that SAX uses, some
+       # entity boundaries cannot be reported under any
+       # circumstances:
+       #
+       # * general entities within attribute values
+       # * parameter entities within declarations
+       #
+       # These will be silently expanded, with no indication of where
+       # the original entity boundaries were.
+       #
+       # Note also that the boundaries of character references (which
+       # are not really entities anyway) are not reported.
+       #
+       # All `start_entity` and `end_entity` events must be properly nested.
+       #
+       # Parameter:
+       #
+       # * `name`: name of the entity. If it is a parameter
+       # entity, the name will begin with `%`, and if it is the
+       # external DTD subset, it will be `[dtd]`.
+       #
+       # SEE: `end_entity`
+       #
+       # SEE: `sax::ext::DeclHandler.internal_entity_decl`
+       #
+       # SEE: `sax::ext::DeclHandler.external_entity_decl`
+       fun start_entity(name: String) do end
+
+       # Report the end of an entity.
+       #
+       # * `name`: name of the entity that is ending.
+       #
+       # SEE: `start_entity`
+       fun end_entity(name: String) do end
+
+       # Report the start of a `CDATA` section.
+       #
+       # The contents of the `CDATA` section will be reported through
+       # the regular `characters` event; this event is intended only to report
+       # the boundary.
+       #
+       # SEE: `end_cdata`
+       fun start_cdata do end
+
+       # Report the end of a CDATA section.
+       #
+       # SEE: `start_cdata`
+       fun end_cdata do end
+
+       # Report an XML comment anywhere in the document.
+       #
+       # This callback will be used for comments inside or outside the
+       # document element, including comments in the external DTD
+       # subset (if read). Comments in the DTD must be properly
+       # nested inside `start/end_dtd` and `start/end_entity` events (if
+       # used).
+       #
+       # Parameters:
+       #
+       # * `str`: characters in the comment.
+       fun comment(str: String) do end
+end
diff --git a/lib/sax/helpers/attributes_impl.nit b/lib/sax/helpers/attributes_impl.nit
new file mode 100644 (file)
index 0000000..ce84faf
--- /dev/null
@@ -0,0 +1,430 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Default implementation of the Attributes interface.
+module sax::attributes_impl
+
+import sax::attributes
+
+# Default implementation of the Attributes interface.
+#
+# This class provides a default implementation of the SAX2
+# `Attributes` interface, with the addition of manipulators so that the list
+# can be modified or reused.
+#
+# There are two typical uses of this class:
+#
+# * to take a persistent snapshot of an Attributes object in a `start_element`
+# event; or
+# * to construct or modify an Attributes object in a SAX2 driver or filter.
+#
+# Note: The original source code and documentation of this class comes, in part,
+# from [SAX 2.0](http://www.saxproject.org).
+class AttributesImpl
+       super Attributes
+
+       private var data: Array[String] = new Array[String]
+       redef var length: Int = 0
+
+       redef fun uri(index: Int): nullable String do
+               if index >= 0 and index < length then
+                       return data[index * 5]
+               else
+                       return null
+               end
+       end
+
+       redef fun local_name(index: Int): nullable String do
+               if index >= 0 and index < length then
+                       return data[index * 5 + 1]
+               else
+                       return null
+               end
+       end
+
+       redef fun qname(index: Int): nullable String do
+               if index >= 0 and index < length then
+                       return data[index * 5 + 2]
+               else
+                       return null
+               end
+       end
+
+       # Look up an attribute's type by index.
+       #
+       # The attribute type is one of the strings `CDATA`, `ID`,
+       # `IDREF`, `IDREFS`, `NMTOKEN`, `NMTOKENS`, `ENTITY`, `ENTITIES`,
+       # or `NOTATION` (always in upper case).
+       #
+       # If the parser has not read a declaration for the attribute,
+       # or if the parser does not report attribute types, then it must
+       # return the value `CDATA` as stated in the XML 1.0 Recommentation
+       # (clause 3.3.3, "Attribute-Value Normalization").
+       #
+       # For an enumerated attribute that is not a notation, the
+       # parser will report the type as `NMTOKEN`.
+       #
+       # Parameters:
+       #
+       # * `index: Int`: attribute index.
+       # * `index: String`: XML 1.0 qualified (prefixed) name.
+       # In many cases, it will be more efficient to look up the name once and
+       # query by `Int` index rather than quering by name repeatedly.
+       #
+       # Returns:
+       #
+       # The attribute's type as a string, or `null` if the specified
+       # attribute is not in the list or if qualified names
+       # are not available.
+       #
+       # SEE: `length`
+       redef fun type_of(index): nullable String do
+               if index isa Int then
+                       if index >= 0 and index < length then
+                               return data[index * 5 + 3]
+                       end
+               else if index isa String and "" != index then
+                       var i: Int = 0
+
+                       while i < data.length do
+                               if data[i + 2] == index then
+                                       return data[i + 3]
+                               end
+                               i += 5
+                       end
+               end
+               return null
+       end
+
+       # Look up an attribute's value by index.
+       #
+       # If the attribute value is a list of tokens (`IDREFS`,
+       # `ENTITIES`, or `NMTOKENS`), the tokens will be concatenated
+       # into a single string with each token separated by a
+       # single space.
+       #
+       # Parameters:
+       #
+       # * `index: Int`: attribute index.
+       # * `index: String`: XML 1.0 qualified (prefixed) name.
+       # In many cases, it will be more efficient to look up the name once and
+       # query by `Int` index rather than quering by name repeatedly.
+       #
+       # Returns:
+       #
+       # The attribute's value as a string, or `null` if the specified
+       # attribute is not in the list or if qualified names
+       # are not available.
+       #
+       # SEE: `length`
+       redef fun value_of(index): nullable String do
+               if index isa Int then
+                       if index >= 0 and index < length then
+                               return data[index * 5 + 4]
+                       end
+               else if index isa String and "" != index then
+                       var i: Int = 0
+
+                       while i < data.length do
+                               if data[i + 2] == index then
+                                       return data[i + 4]
+                               end
+                               i += 5
+                       end
+               end
+               return null
+       end
+
+       # Look up the index of an attribute by Namespace name.
+       #
+       # In many cases, it will be more efficient to look up the name once and
+       # query by `Int` index rather than quering by name repeatedly.
+       #
+       # Parameters:
+       #
+       # * `uri`: Namespace URI, or the empty string if
+       # the name has no Namespace URI.
+       # * `local_name`: attribute's local name.
+       #
+       # Returns:
+       #
+       # The index of the attribute, or -1 if it does not
+       # appear in the list.
+       redef fun index_ns(uri: String, local_name: String): Int do
+               var i: Int = 0
+
+               if "" != local_name then
+                       while i < data.length do
+                               if data[i] == uri and data[i + 1] == local_name then
+                                       return i / 5
+                               end
+                               i += 5
+                       end
+               end
+               return -1
+       end
+
+       # Look up the index of an attribute by XML 1.0 qualified name.
+       #
+       # In many cases, it will be more efficient to look up the name once and
+       # query by `Int` index rather than quering by name repeatedly.
+       #
+       # Parameters:
+       #
+       # * `qname`: XML 1.0 qualified (prefixed) name.
+       #
+       # Returns:
+       #
+       # The index of the attribute, or -1 if it does not
+       # appear in the list.
+       redef fun index_of(qname: String): Int do
+               var i: Int = 0
+
+               if "" != qname then
+                       while i < data.length do
+                               if data[i + 2] == qname then
+                                       return i / 5
+                               end
+                               i += 5
+                       end
+               end
+               return -1
+       end
+
+       # Look up an attribute's type by Namespace name.
+       #
+       # In many cases, it will be more efficient to look up the name once and
+       # query by `Int` index rather than quering by name repeatedly.
+       #
+       # See `type_of` for a description
+       # of the possible types.
+       #
+       # Parameters:
+       #
+       # * `uri`: Namespace URI, or the empty string if
+       # the name has no Namespace URI.
+       #
+       # * `local_name`: attribute's local name.
+       #
+       # Returns:
+       #
+       # The attribute type as a string, or `null` if the
+       # attribute is not in the list or if Namespace
+       # processing is not being performed.
+       redef fun type_ns(uri: String, local_name: String): nullable String do
+               var i: Int = 0
+
+               if "" != local_name then
+                       while i < data.length do
+                               if data[i] == uri and data[i + 1] == local_name then
+                                       return data[i + 3]
+                               end
+                               i += 5
+                       end
+               end
+               return null
+       end
+
+       # Look up an attribute's value by Namespace name.
+       #
+       # In many cases, it will be more efficient to look up the name once and
+       # query by `Int` index rather than quering by name repeatedly.
+       #
+       # See `value_of` for a description
+       # of the possible values.
+       #
+       # Parameters:
+       #
+       # * `uri`: Namespace URI, or the empty string if
+       # the name has no Namespace URI.
+       #
+       # * `local_name`: attribute's local name.
+       #
+       # Returns:
+       #
+       # The attribute value as a string, or `null` if the
+       # attribute is not in the list or if Namespace
+       # processing is not being performed.
+       redef fun value_ns(uri: String, local_name: String): nullable String do
+               var i: Int = 0
+
+               if "" != local_name then
+                       while i < data.length do
+                               if data[i] == uri and data[i + 1] == local_name then
+                                       return data[i + 4]
+                               end
+                               i += 5
+                       end
+               end
+               return null
+       end
+
+       # Clear the attribute list for reuse.
+       fun clear do
+               data.clear
+               length = 0
+       end
+
+       # Copy an entire Attributes object.
+       #
+       # It may be more efficient to reuse an existing object
+       # rather than constantly allocating new ones.
+       #
+       # Parameters:
+       #
+       # * `atts`: attributes to copy.
+       fun attributes=(atts: Attributes) do
+               var i: Int = 0
+
+               clear
+               length = atts.length
+               data.enlarge(length * 5)
+               while i < length do
+                       data.push(atts.uri(i).as(not null))
+                       data.push(atts.local_name(i).as(not null))
+                       data.push(atts.qname(i).as(not null))
+                       data.push(atts.type_of(i).as(not null))
+                       data.push(atts.value_of(i).as(not null))
+                       i += 1
+               end
+       end
+
+       # Add an attribute to the end of the list.
+       #
+       # For the sake of speed, this method does no checking
+       # to see if the attribute is already in the list: that is
+       # the responsibility of the application.
+       #
+       # Parameters:
+       #
+       # * `uri`: Namespace URI, or the empty string if
+       # none is available or Namespace processing is not being performed.
+       # * `local_name`: local name, or the empty string if
+       # Namespace processing is not being performed.
+       # * `qname`: qualified (prefixed) name, or the empty string
+       # if qualified names are not available.
+       # * `attribute_type`: attribute type as a string.
+       # * `value`: attribute value.
+       fun add(uri: String, local_name: String, qname: String,
+                       attribute_type: String, value: String) do
+               ensure_capacity(length + 1)
+               data.push(uri)
+               data.push(local_name)
+               data.push(qname)
+               data.push(attribute_type)
+               data.push(value)
+               length += 1
+       end
+
+       # Set an attribute in the list.
+       #
+       # For the sake of speed, this method does no checking
+       # for name conflicts or well-formedness: such checks are the
+       # responsibility of the application.
+       #
+       # Parameters:
+       #
+       # * `index`: index of the attribute (zero-based).
+       # * `uri`: Namespace URI, or the empty string if
+       # none is available or Namespace processing is not being performed.
+       # * `local_name`: local name, or the empty string if
+       # Namespace processing is not being performed.
+       # * `qname`: qualified (prefixed) name, or the empty string
+       # if qualified names are not available.
+       # * `attribute_type`: attribute type as a string.
+       # * `value`: attribute value.
+       fun set(index: Int, uri: String, local_name: String, qname: String,
+                       attribute_type: String, value: String) do
+               assert index_in_bounds: index >= 0 and index < length
+               data[index * 5] = uri
+               data[index * 5 + 1] = local_name
+               data[index * 5 + 2] = qname
+               data[index * 5 + 3] = attribute_type
+               data[index * 5 + 4] = value
+       end
+
+       # Remove an attribute from the list.
+       #
+       # Parameters:
+       #
+       # * `index`: index of the attribute (zero-based).
+       fun remove_at(index: Int) do
+               assert index_in_bounds: index >= 0 and index < length
+               index = index * 5
+               for i in [1..5] do
+                       data.remove_at(index)
+               end
+               length -= 1
+       end
+
+       # Set the Namespace URI of a specific attribute.
+       #
+       # Parameters:
+       #
+       # * `index`: index of the attribute (zero-based).
+       # * `uri`: attribute's Namespace URI, or the empty string for none.
+       fun uri=(index: Int, uri: String) do
+               assert index_in_bounds: index >= 0 and index < length
+               data[index * 5] = uri
+       end
+
+       # Set the local name of a specific attribute.
+       #
+       # Parameters:
+       #
+       # * `index`: index of the attribute (zero-based).
+       # * `local_name`: attribute's local name, or the empty string for none.
+       fun local_name=(index: Int, local_name: String) do
+               assert index_in_bounds: index >= 0 and index < length
+               data[index * 5 + 1] = local_name
+       end
+
+       # Set the qualified name of a specific attribute.
+       #
+       # Parameters:
+       #
+       # * `index`: index of the attribute (zero-based).
+       # * `qname`: attribute's qualified name, or the empty string for none.
+       fun qname=(index: Int, qname: String) do
+               assert index_in_bounds: index >= 0 and index < length
+               data[index * 5 + 2] = qname
+       end
+
+       # Set the type of a specific attribute.
+       #
+       # Parameters:
+       #
+       # * `index`: index of the attribute (zero-based).
+       # * `attribute_type`: attribute's type.
+       fun type_of=(index: Int, attribute_type: String) do
+               assert index_in_bounds: index >= 0 and index < length
+               data[index * 5 + 3] = attribute_type
+       end
+
+       # Set the value of a specific attribute.
+       #
+       # Parameters:
+       #
+       # * `index`: index of the attribute (zero-based).
+       # * `value`: attribute's value.
+       fun value_of=(index: Int, value: String) do
+               assert index_in_bounds: index >= 0 and index < length
+               data[index * 5 + 4] = value
+       end
+
+       # Ensure the internal array's capacity.
+       #
+       # Parameters:
+       #
+       # * `n`: minimum number of attributes that the array must be able to hold.
+       private fun ensure_capacity(n: Int) do
+               data.enlarge(n * 5)
+       end
+end
diff --git a/lib/sax/helpers/helpers.nit b/lib/sax/helpers/helpers.nit
new file mode 100644 (file)
index 0000000..9e47d06
--- /dev/null
@@ -0,0 +1,23 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Contains "helper" classes, including support for bootstrapping SAX-based applications.
+#
+# See [http://www.saxproject.org](http://www.saxproject.org)
+# for more information about SAX.
+#
+# Note: The original documentation comes from [SAX 2.0](http://www.saxproject.org).
+module sax::helpers
+
+import sax
+import attributes_impl
+import namespace_support
+import sax_locator_impl
+import xml_filter_impl
diff --git a/lib/sax/helpers/namespace_support.nit b/lib/sax/helpers/namespace_support.nit
new file mode 100644 (file)
index 0000000..e682321
--- /dev/null
@@ -0,0 +1,665 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Encapsulates Namespace logic for use by applications using SAX, or internally by SAX drivers.
+module sax::helpers::namespace_support
+
+# Encapsulates Namespace logic for use by applications using SAX, or internally by SAX drivers.
+#
+# This class encapsulates the logic of Namespace processing:
+# it tracks the declarations currently in force for each context
+# and automatically processes qualified XML names into their
+# Namespace parts; it can also be used in reverse for generating
+# XML qualified names from Namespaces.
+#
+# Namespace support objects are reusable, but the reset method
+# must be invoked between each session.
+#
+# Here is a simple session:
+#
+#     var parts: Array[String] = new Array[String].with_capacity(3)
+#     var support = new NamespaceSupport
+#     #
+#     support.push_context
+#     support.declare_prefix("", "http://www.w3.org/1999/xhtml")
+#     support.declare_prefix("dc", "http://www.purl.org/dc#")
+#     #
+#     parts = support.process_name("p", parts, false).as(not null)
+#     assert parts[0] == "http://www.w3.org/1999/xhtml"
+#     assert parts[1] == "p"
+#     assert parts[2] == "p"
+#     #
+#     parts = support.process_name("dc:title", parts, false).as(not null)
+#     assert parts[0] == "http://www.purl.org/dc#"
+#     assert parts[1] == "title"
+#     assert parts[2] == "dc:title"
+#     #
+#     support.pop_context
+#
+# Note that this class is optimized for the use case where most
+# elements do not contain Namespace declarations: if the same
+# prefix/URI mapping is repeated for each context (for example), this
+# class will be somewhat less efficient.
+#
+# Although SAX drivers (parsers) may choose to use this class to
+# implement namespace handling, they are not required to do so.
+# Applications must track namespace information themselves if they
+# want to use namespace information.
+#
+# Note: The original source code and documentation of this class comes, in part,
+# from [SAX 2.0](http://www.saxproject.org).
+class NamespaceSupport
+       # The XML Namespace URI as a constant.
+       #
+       # The value is `http://www.w3.org/XML/1998/namespace`
+       # as defined in the “Namespaces in XML” specification.
+       #
+       # This is the Namespace URI that is automatically mapped
+       # to the `xml` prefix.
+       var xmlns = "http://www.w3.org/XML/1998/namespace"
+
+       # The namespace declaration URI as a constant.
+       #
+       # The value is `http://www.w3.org/xmlns/2000/`, as defined
+       # in a erratum to the “Namespaces in XML” recommendation.
+       #
+       # This is the Namespace URI that is automatically mapped
+       # to the `xmlns` prefix.
+       var nsdecl = "http://www.w3.org/xmlns/2000/"
+
+       private var contexts: Array[Context] = new Array[Context].with_capacity(32)
+       private var current_context: Context = new Context
+       private var context_position: Int = 0
+
+       init do
+               contexts.push(current_context)
+               current_context.declare_prefix("xml", xmlns)
+               current_context.declare_prefix("xmlns", nsdecl)
+       end
+
+       # Reset this Namespace support object for reuse.
+       #
+       # It is necessary to invoke this method before reusing the
+       # Namespace support object for a new session.
+       fun reset do
+               contexts.clear
+               contexts.enlarge(32)
+               context_position = 0
+               current_context = new Context
+               contexts.push(current_context)
+               current_context.declare_prefix("xml", xmlns)
+               current_context.declare_prefix("xmlns", nsdecl)
+       end
+
+       # Start a new Namespace context.
+       #
+       # The new context will automatically inherit
+       # the declarations of its parent context, but it will also keep
+       # track of which declarations were made within this context.
+       #
+       # Event callback code should start a new context once per element.
+       # This means being ready to call this in either of two places.
+       # For elements that don't include namespace declarations, the
+       # `ContentHandler.start_element` callback is the right place.
+       # For elements with such a declaration, it'd done in the first
+       # `ContentHandler.start_prefix_mapping` callback.
+       # A boolean flag can be used to
+       # track whether a context has been started yet. When either of
+       # those methods is called, it checks the flag to see if a new context
+       # needs to be started. If so, it starts the context and sets the
+       # flag. After `ContentHandler.start_element` does that, it always clears
+       # the flag.
+       #
+       # Normally, SAX drivers would push a new context at the beginning
+       # of each XML element. Then they perform a first pass over the
+       # attributes to process all namespace declarations, making
+       # `ContentHandler.start_prefix_mapping` callbacks.
+       # Then a second pass is made, to determine the namespace-qualified
+       # names for all attributes and for the element name.
+       # Finally all the information for the
+       # `ContentHandler.start_element` callback is available,
+       # so it can then be made.
+       #
+       # The Namespace support object always starts with a base context
+       # already in force: in this context, only the `xml` prefix is
+       # declared.
+       #
+       # SEE: `sax::ContentHandler`
+       #
+       # SEE: `pop_context`
+       fun push_context do
+               current_context.decls_ok = false
+               context_position += 1
+
+               # Extend the array if necessary.
+               if context_position >= contexts.length then
+                       current_context = new Context
+                       contexts.push(current_context)
+               else
+                       current_context = contexts[context_position]
+               end
+
+               # Set the parent, if any.
+               if context_position > 0 then
+                       current_context.parent = contexts[context_position - 1]
+               end
+       end
+
+       # Revert to the previous Namespace context.
+       #
+       # Normally, you should pop the context at the end of each
+       # XML element. After popping the context, all Namespace prefix
+       # mappings that were previously in force are restored.
+       #
+       # You must not attempt to declare additional Namespace
+       # prefixes after popping a context, unless you push another
+       # context first.
+       #
+       # SEE: `push_context`
+       fun pop_context do
+               assert stack_not_empty: context_position > 0
+               current_context.clear
+               context_position -= 1
+               current_context = contexts[context_position]
+       end
+
+       # Declare a Namespace prefix.
+       #
+       # All prefixes must be declared before they are referenced.
+       # For example, a SAX driver (parser)
+       # would scan an element's attributes
+       # in two passes: first for namespace declarations,
+       # then a second pass using `process_name` to
+       # interpret prefixes against (potentially redefined) prefixes.
+       #
+       # This method declares a prefix in the current Namespace
+       # context; the prefix will remain in force until this context
+       # is popped, unless it is shadowed in a descendant context.
+       #
+       # To declare the default element Namespace, use the empty string as
+       # the prefix.
+       #
+       # Note that you must *not* declare a prefix after
+       # you've pushed and popped another Namespace context, or
+       # treated the declarations phase as complete by processing
+       # a prefixed name.
+       #
+       # Note that there is an asymmetry in this library:
+       # `prefix` will not return the `""` prefix,
+       # even if you have declared a default element namespace.
+       # To check for a default namespace,
+       # you have to look it up explicitly using `uri`.
+       # This asymmetry exists to make it easier to look up prefixes
+       # for attribute names, where the default prefix is not allowed.
+       #
+       # Parameters:
+       #
+       # * `prefix`: prefix to declare, or the empty string to
+       # indicate the default element namespace. This may never have
+       # the value `xml` or `xmlns`.
+       # * `uri`: The Namespace URI to associate with the prefix.
+       #
+       #
+       # Returns:
+       #
+       # `true` if the prefix and the URI are legal, `false` otherwise.
+       #
+       # SEE: `process_name`
+       #
+       # SEE: `uri`
+       #
+       # SEE: `prefix`
+       fun declare_prefix(prefix: String, uri: String): Bool do
+               if prefix == "xml" or prefix == "xmlns" or
+                                       uri == xmlns or uri == nsdecl then
+                       return false
+               else
+                       current_context.declare_prefix(prefix, uri)
+                       return true
+               end
+       end
+
+       # Process a raw XML qualified name, after all declarations in the current context have been handled by `declare_prefix`.
+       #
+       # This method processes a raw XML qualified name in the current
+       # context by removing the prefix and looking it up among the
+       # prefixes currently declared. The return value will be the
+       # array supplied by the caller, filled in as follows:
+       #
+       # * `parts[0]`: Namespace URI, or an empty string if none is in use.
+       # * `parts[1]`: local name (without prefix).
+       # * `parts[2]`: original raw name.
+       #
+       # If the raw name has a prefix that has not been declared, then
+       # the return value will be `null`.
+       #
+       # Note that attribute names are processed differently than
+       # element names: an unprefixed element name will receive the
+       # default Namespace (if any), while an unprefixed attribute name
+       # will not.
+       #
+       # Parameters:
+       #
+       # * `qname`: raw XML qualified name to be processed.
+       # * `parts`: array supplied by the caller. Will be enlarged to 3 elements if
+       # needed. If the specified array contains more than 3 elements, its length
+       # will be kept intact.
+       # * `is_attribute`: flag indicating whether this is an attribute name
+       # (`true`) or an element name (`false`).
+       #
+       # SEE: `declare_prefix`
+       fun process_name(qname: String, parts: Array[String], is_attribute: Bool):
+                       nullable Array[String] do
+               var my_parts = current_context.process_name(qname, is_attribute)
+
+               if my_parts == null then
+                       return null
+               else
+                       parts[0] = my_parts[0]
+                       parts[1] = my_parts[1]
+                       parts[2] = my_parts[2]
+                       if parts[0] == "" and qname == "xmlns" and is_attribute then
+                               parts[0] = nsdecl
+                               parts[1] = ""
+                       end
+                       return parts
+               end
+       end
+
+       # Look up a prefix and get the currently-mapped Namespace URI.
+       #
+       # This method looks up the prefix in the current context.
+       # Use the empty string (`""`) for the default Namespace.
+       #
+       # Parameters:
+       #
+       # * `prefix`: The prefix to look up.
+       #
+       # Returns:
+       #
+       # The associated Namespace URI, or `null` if the prefix
+       # is undeclared in this context.
+       #
+       # SEE: `prefix`
+       #
+       # SEE: `prefixes_of`
+       fun uri(prefix: String): nullable String do
+               return current_context.uri(prefix)
+       end
+
+       # Return all prefixes currently declared.
+       #
+       # Note: if there is a default prefix, it will not be
+       # returned in this enumeration; check for the default prefix
+       # using the `uri` with an argument of `""` or use `declared_prefixes`.
+       #
+       # Returns:
+       #
+       # All prefixes declared in the current context except
+       # for the empty (default) prefix.
+       #
+       # SEE: `declared_prefixes`
+       #
+       # SEE: `uri`
+       fun prefixes: Collection[String] do return current_context.prefixes
+
+       # Return one of the prefixes mapped to a Namespace URI.
+       #
+       # If more than one prefix is currently mapped to the same
+       # URI, this method will make an arbitrary selection; if you
+       # want all of the prefixes, use the `prefixes_of` method instead.
+       #
+       # Note: this will never return the empty (default) prefix;
+       # to check for a default prefix, use the `uri`
+       # method with an argument of `""`.
+       #
+       # Parameters:
+       #
+       # * `uri`: Namespace URI.
+       #
+       # Returns:
+       #
+       # One of the prefixes currently mapped to the URI supplied,
+       # or `null` if none is mapped or if the URI is assigned to
+       # the default Namespace.
+       #
+       # SEE: `prefixes_of`
+       #
+       # SEE: `uri`
+       fun prefix(uri: String): nullable String do
+               return current_context.prefix(uri)
+       end
+
+       # Return all prefixes currently declared for an URI.
+       #
+       # This method returns prefixes mapped to a specific Namespace
+       # URI. The `xml` prefix will be included. If you want only one
+       # prefix that's mapped to the Namespace URI, and you don't care
+       # which one you get, use the `prefix` method instead.
+       #
+       # Note: the empty (default) prefix is *never* included
+       # in this enumeration; to check for the presence of a default
+       # Namespace, use the `uri` method with an argument of `""`.
+       #
+       # Parameters:
+       #
+       # * `uri`: The Namespace URI.
+       #
+       # SEE: `prefix`
+       #
+       # SEE: `declared_prefixes`
+       #
+       # SEE: `uri`
+       fun prefixes_of(uri: String): Collection[String] do
+               var prefixes = new Array[String]
+               var all_prefixes = self.prefixes
+
+               for prefix in all_prefixes do
+                       if uri == self.uri(prefix) then
+                               prefixes.push(prefix)
+                       end
+               end
+               return prefixes
+       end
+
+       # Return all prefixes declared (and undeclared) in this context.
+       #
+       # The empty (default) prefix will be included in this
+       # enumeration; note that this behaviour differs from that of
+       # `prefix`, `prefixes` and `prefixes_of`.
+       #
+       # SEE: `prefixes`
+       #
+       # SEE: `uri`
+       fun declared_prefixes: Collection[String] do
+               return current_context.declared_prefixes
+       end
+end
+
+
+# Internal class for a single Namespace context.
+#
+# This module caches and reuses Namespace contexts,
+# so the number allocated
+# will be equal to the element depth of the document, not to the total
+# number of elements (i.e. 5-10 rather than tens of thousands).
+# Also, data structures used to represent contexts are shared when
+# possible (child contexts without declarations) to further reduce
+# the amount of memory that's consumed.
+#
+# Note: The original source code and documentation of this class comes, in part,
+# from [SAX 2.0](http://www.saxproject.org).
+private class Context
+
+       private var empty: Collection[String] = new Array[String].with_capacity(0)
+
+       # `prefix` -> `uri`
+       private var prefix_table: nullable Map[String, String] = null
+
+       # Cache of `process_name` for elements.
+       #
+       # `qname -> [uri, local_name, qname]`
+       private var element_name_table: nullable Map[String, Array[String]] = null
+
+       # Cache of `process_name` for attributes.
+       #
+       # `qname -> [uri, local_name, qname]`
+       private var attribute_name_table: nullable Map[String, Array[String]] = null
+
+       # Namespace in absence of prefix.
+       private var default_ns: nullable String = null
+
+       # Can we currently declare prefixes in this context?
+       var decls_ok: Bool = true is writable
+
+       # All prefixes declared in this context.
+       private var declarations: nullable Array[String] = null
+
+       # Was `copy_tables` called since the last call to `parent=`?
+       private var decl_seen: Bool = false
+
+       # Parent context.
+       private var p_parent: nullable Context = null
+
+       init do
+       end
+
+       # (Re)set the parent of this Namespace context.
+       #
+       # The context must either have been freshly constructed,
+       # or must have been cleared.
+       #
+       # Parameters:
+       #
+       # * `context`: parent Namespace context object.
+       fun parent=(parent: Context) do
+               p_parent = parent
+               declarations = null
+               prefix_table = parent.prefix_table
+               element_name_table = parent.element_name_table
+               attribute_name_table = parent.attribute_name_table
+               default_ns = parent.default_ns
+               decl_seen = false
+               decls_ok = true
+       end
+
+       # Makes associated state become collectible, invalidating this context.
+       #
+       # `parent=` must be called before this context may be used again.
+       fun clear do
+               p_parent = null
+               prefix_table = null
+               element_name_table = null
+               attribute_name_table = null
+               default_ns = null
+               declarations = null
+       end
+
+       # Declare a Namespace prefix for this context.
+       #
+       # Parameters:
+       #
+       # * `prefix`: prefix to declare.
+       # * `uri`: associated Namespace URI.
+       #
+       # SEE: `NamespaceSupport.declare_prefix`
+       fun declare_prefix(prefix: String, uri: String) do
+               assert legal_state: decls_ok else
+                       sys.stderr.write("Can't declare any more prefixes in this context.\n")
+               end
+
+               # Lazy processing...
+               if not decl_seen then
+                       copy_tables
+               end
+
+               if "" == prefix then
+                       if "" == uri then
+                               default_ns = null
+                       else
+                               default_ns = uri
+                       end
+               else if "" == uri then
+                       prefix_table.keys.remove(prefix)
+               else
+                       prefix_table[prefix] = uri
+               end
+               declarations.push(prefix)
+       end
+
+       # Process a raw XML qualified name in this context.
+       #
+       # Parameters:
+       #
+       # * `qname`: raw XML qualified name.
+       # * `is_attribute`: `true` if this is an attribute name.
+       #
+       # Returns:
+       #
+       # An array of three strings containing the URI part (or empty string),
+       # the local part and the raw name, or `null` if there is an undeclared
+       # prefix.
+       #
+       # SEE: `NamespaceSupport.process_name`
+       fun process_name(qname: String, is_attribute: Bool):
+                       nullable Array[String] do
+               var name: Array[String]
+               var table: Map[String, Array[String]]
+               var match: nullable Match
+
+               # Detect errors in call sequence.
+               decls_ok = false
+               # Select the appropriate table.
+               if is_attribute then
+                       table = attribute_name_table.as(not null)
+               else
+                       table = element_name_table.as(not null)
+               end
+
+               # Start by looking in the cache, and
+               # return immediately if the name
+               # is already known in this content.
+               if table.keys.has(qname) then
+                       return table[qname]
+               end
+
+               # We haven't seen this name in this
+               # context before. Maybe in the parent
+               # context, but we can't assume prefix
+               # bindings are the same.
+               name = new Array[String].with_capacity(3)
+               match = qname.search(':')
+
+               if match == null then
+                       # No prefix
+                       if is_attribute then
+                               name.push("")
+                       else
+                               name.push(default_ns or else "")
+                       end
+                       name.push(qname)
+                       name.push(qname)
+               else
+                       # Prefix
+                       var prefix = qname.substring(0, match.from)
+
+                       if prefix == "" then
+                               if is_attribute then
+                                       name.push("")
+                               else
+                                       name.push(default_ns or else "")
+                               end
+                               name.push(qname.substring_from(match.after))
+                               name.push(qname)
+                       else if (not is_attribute) and "xmlns" == prefix then
+                               return null
+                       else if prefix_table.keys.has(prefix) then
+                               name.push(prefix_table[prefix])
+                               name.push(qname.substring_from(match.after))
+                               name.push(qname)
+                       else
+                               return null
+                       end
+               end
+
+               # Save in the cache for future use.
+               # (Could be shared with parent context...)
+               table[qname] = name
+               return name
+       end
+
+       # Look up the URI associated with a prefix in this context.
+       #
+       # Return `null` if no URI is associated with a specified prefix.
+       #
+       # Parameters:
+       #
+       # * `prefix`: prefix to look up.
+       #
+       # SEE: `NamespaceSupport.uri`
+       fun uri(prefix: String): nullable String do
+               if "" == prefix then
+                       return default_ns
+               else if prefix_table == null then
+                       return null
+               else
+                       return prefix_table.get_or_null(prefix)
+               end
+       end
+
+       # Look up one of the prefixes associated with a URI in this context.
+       #
+       # Since many prefixes may be mapped to the same URI,
+       # the return value may be unreliable.
+       #
+       # Parameters:
+       #
+       # * `uri`: URI to look up.
+       #
+       # Returns:
+       #
+       # The associated prefix, or `null` if none is declared.
+       #
+       # SEE: `NamespaceSupport.prefix`
+       fun prefix(uri: String): nullable String do
+               # Note: We do not use the original code from SAX 2.0.1 because it is
+               # buggy with redefined prefixes. For example, with
+               # `<x xmlns:y="1"><z xmlns:y="2" /></x>`, when in `z`, `uri("1")`
+               # returns `"y"` in the original code while it should return `null`.
+               # Our code is slower, but it works.
+               var all_prefixes = prefixes
+
+               for prefix in all_prefixes do
+                       if uri == self.uri(prefix) then
+                               return prefix
+                       end
+               end
+               return null
+       end
+
+       # Return all prefixes declared in this context (possibly empty).
+       #
+       # SEE: `NamespaceSupport.declared_prefixes`
+       fun declared_prefixes: Collection[String] do
+               return declarations or else empty
+       end
+
+       # Return all prefixes currently in force.
+       #
+       # The default prefix, if in force, is *not*
+       # returned, and will have to be checked for separately.
+       #
+       # SEE: `NamespaceSupport.prefixes`
+       fun prefixes: Collection[String] do
+               if prefix_table == null then
+                       return empty
+               else
+                       return prefix_table.keys
+               end
+       end
+
+       # Copy on write for the internal tables in this context.
+       #
+       # This class is optimized for the normal case where most
+       # elements do not contain Namespace declarations.
+       private fun copy_tables do
+               if prefix_table != null then
+                       var old_prefix_table = prefix_table.as(not null)
+                       prefix_table = new HashMap[String, String]
+                       prefix_table.recover_with(old_prefix_table)
+               else
+                       prefix_table = new HashMap[String, String]
+               end
+               element_name_table = new HashMap[String, Array[String]]
+               attribute_name_table = new HashMap[String, Array[String]]
+               declarations = new Array[String]
+               decl_seen = true
+       end
+end
diff --git a/lib/sax/helpers/sax_locator_impl.nit b/lib/sax/helpers/sax_locator_impl.nit
new file mode 100644 (file)
index 0000000..c736a46
--- /dev/null
@@ -0,0 +1,78 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Provides an optional convenience implementation of `Locator`.
+module sax::helpers::sax_locator_impl
+
+import sax::sax_locator
+
+# Provides an optional convenience implementation of `Locator`.
+#
+# This class is available mainly for application writers, who
+# can use it to make a persistent snapshot of a locator at any
+# point during a document parse:
+#
+#     module example
+#     #
+#     import sax::helpers::SAXLocatorImpl
+#     import sax::ContentHandler
+#     #
+#     class Example super ContentHandler
+#      private var _locator: nullable SAXLocator = null
+#      private var start_loc: nullable SAXLocator = null
+#     #
+#      fun locator=(Locator locator) do
+#              # note the locator
+#              _locator = locator
+#      end
+#     #
+#      fun start_document do
+#              # save the location of the start of the document
+#              # for future use.
+#              start_loc = new SAXLocatorImpl.from(locator)
+#      end
+#     end
+#
+# Normally, parser writers will not use this class, since it
+# is more efficient to provide location information only when
+# requested, rather than constantly updating a `Locator` object.
+#
+# Note: The original source code and documentation of this class comes, in part,
+# from [SAX 2.0](http://www.saxproject.org).
+class SAXLocatorImpl super SAXLocator
+       redef var public_id: nullable String = null is writable
+       redef var system_id: nullable String = null is writable
+       redef var line_number: Int = -1 is writable
+       redef var column_number: Int = -1 is writable
+
+       # Zero-argument constructor.
+       #
+       # This will not normally be useful, since the main purpose
+       # of this class is to make a snapshot of an existing Locator.
+       init do
+       end
+
+       # Copy constructor.
+       #
+       # Create a persistent copy of the current state of a locator.
+       # When the original locator changes, this copy will still keep
+       # the original values (and it can be used outside the scope of
+       # `ContentHandler` methods).
+       #
+       # Parameters:
+       #
+       # * `locator`: locator to copy.
+       init with(locator: SAXLocator) do
+               public_id = locator.public_id
+               system_id = locator.system_id
+               line_number = locator.line_number
+               column_number = locator.column_number
+       end
+end
diff --git a/lib/sax/helpers/test_attributes_impl.nit b/lib/sax/helpers/test_attributes_impl.nit
new file mode 100644 (file)
index 0000000..dc7ecd2
--- /dev/null
@@ -0,0 +1,260 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Test suite for `attributes_impl`.
+module test_attributes_impl is test_suite
+
+import test_suite
+import sax::helpers::attributes_impl
+
+class TestAttributesImpl
+       super TestSuite
+
+       private fun sample: AttributesImpl do
+               var subject: AttributesImpl = new AttributesImpl
+
+               # The parser may include everything...
+               subject.add("http://example.com/", "bar", "foo:bar", "CDATA", "baz")
+               # ... or omit the `qname`...
+               subject.add("urn:is:not:often:used", "i-am_ME", "", "ID", "noop")
+               # ... or retrieve an attribute with an empty value (that *must* be kept intact)...
+               subject.add("http://www.w3.org/XML/1998/namespace", "lang", "xml:lang",
+                               "NMTOKEN", "")
+               # ... or omit to process the namespace.
+               subject.add("", "", "xml:space", "NMTOKEN", "default")
+               return subject
+       end
+
+       fun test_length do
+               var subject: AttributesImpl = new AttributesImpl
+
+               assert 0 == subject.length
+               subject.add("http://example.com/", "bar", "foo:bar", "CDATA", "baz")
+               assert 1 == subject.length
+               subject.add("http://example.com/", "bar", "foo:bar", "CDATA", "baz")
+               assert 2 == subject.length
+               subject.clear
+               assert 0 == subject.length
+               # Clearing twice must not produce erroneous data.
+               subject.clear
+               assert 0 == subject.length
+       end
+
+       fun test_uri do
+               var subject: AttributesImpl = sample
+
+               assert "http://example.com/" == subject.uri(0)
+               assert "urn:is:not:often:used" == subject.uri(1)
+               assert "http://www.w3.org/XML/1998/namespace" == subject.uri(2)
+               assert "" == subject.uri(3)
+               assert subject.uri(4) == null
+               assert subject.uri(-1) == null
+               subject.clear
+               assert subject.uri(0) == null
+       end
+
+       fun test_local_name do
+               var subject: AttributesImpl = sample
+
+               assert "bar" == subject.local_name(0)
+               assert "i-am_ME" == subject.local_name(1)
+               assert "lang" == subject.local_name(2)
+               assert "" == subject.local_name(3)
+               assert subject.local_name(4) == null
+               assert subject.local_name(-1) == null
+               subject.clear
+               assert subject.local_name(0) == null
+       end
+
+       fun test_qname do
+               var subject: AttributesImpl = sample
+
+               assert "foo:bar" == subject.qname(0)
+               assert "" == subject.qname(1)
+               assert "xml:lang" == subject.qname(2)
+               assert "xml:space" == subject.qname(3)
+               assert subject.qname(4) == null
+               assert subject.qname(-1) == null
+               subject.clear
+               assert subject.qname(0) == null
+       end
+
+       fun test_type_of do
+               var subject: AttributesImpl = sample
+
+               assert "CDATA" == subject.type_of(0)
+               assert "ID" == subject.type_of(1)
+               assert "NMTOKEN" == subject.type_of(2)
+               assert "NMTOKEN" == subject.type_of(3)
+               assert subject.type_of(4) == null
+               assert subject.type_of(-1) == null
+               subject.clear
+               assert subject.type_of(0) == null
+       end
+
+       fun test_type_of_qname do
+               var subject: AttributesImpl = sample
+
+               assert "CDATA" == subject.type_of("foo:bar")
+               assert subject.type_of("") == null
+               assert "NMTOKEN" == subject.type_of("xml:lang")
+               assert "NMTOKEN" == subject.type_of("xml:space")
+               assert subject.type_of("bob") == null
+               subject.clear
+               assert subject.type_of("xml:lang") == null
+       end
+
+       fun test_value_of do
+               var subject: AttributesImpl = sample
+
+               assert "baz" == subject.value_of(0)
+               assert "noop" == subject.value_of(1)
+               assert "" == subject.value_of(2)
+               assert "default" == subject.value_of(3)
+               assert subject.value_of(4) == null
+               assert subject.value_of(-1) == null
+               subject.clear
+               assert subject.value_of(0) == null
+       end
+
+       fun test_value_of_qname do
+               var subject: AttributesImpl = sample
+
+               assert "baz" == subject.value_of("foo:bar")
+               assert subject.value_of("") == null
+               assert "" == subject.value_of("xml:lang")
+               assert "default" == subject.value_of("xml:space")
+               assert subject.value_of("bob") == null
+               subject.clear
+               assert subject.value_of("xml:lang") == null
+       end
+
+       fun test_index_ns do
+               var subject: AttributesImpl = sample
+
+               assert 0 == subject.index_ns("http://example.com/", "bar")
+               assert 1 == subject.index_ns("urn:is:not:often:used", "i-am_ME")
+               assert 2 == subject.index_ns("http://www.w3.org/XML/1998/namespace", "lang")
+               assert -1 == subject.index_ns("", "")
+               assert -1 == subject.index_ns("http://www.w3.org/XML/1998/namespace", "space")
+               subject.clear
+               assert -1 == subject.index_ns("http://example.com/", "bar")
+       end
+
+       fun test_index_of do
+               var subject: AttributesImpl = sample
+
+               assert 0 == subject.index_of("foo:bar")
+               assert -1 == subject.index_of("")
+               assert 2 == subject.index_of("xml:lang")
+               assert 3 == subject.index_of("xml:space")
+               assert -1 == subject.index_of("i-am_ME")
+               subject.clear
+               assert -1 == subject.index_of("foo:bar")
+       end
+
+       fun test_type_ns do
+               var subject: AttributesImpl = sample
+
+               assert "CDATA" == subject.type_ns("http://example.com/", "bar")
+               assert "ID" == subject.type_ns("urn:is:not:often:used", "i-am_ME")
+               assert "NMTOKEN" == subject.type_ns("http://www.w3.org/XML/1998/namespace", "lang")
+               assert subject.type_ns("", "") == null
+               assert subject.type_ns("http://www.w3.org/XML/1998/namespace", "space") == null
+               subject.clear
+               assert subject.type_ns("http://example.com/", "bar") == null
+       end
+
+       fun test_value_ns do
+               var subject: AttributesImpl = sample
+
+               assert "baz" == subject.value_ns("http://example.com/", "bar")
+               assert "noop" == subject.value_ns("urn:is:not:often:used", "i-am_ME")
+               assert "" == subject.value_ns("http://www.w3.org/XML/1998/namespace", "lang")
+               assert subject.value_ns("", "") == null
+               assert subject.value_ns("http://www.w3.org/XML/1998/namespace", "space") == null
+               subject.clear
+               assert subject.value_ns("http://example.com/", "bar") == null
+       end
+
+       fun test_attributes_set do
+               var subject: AttributesImpl = sample
+               var subject2: AttributesImpl = new AttributesImpl
+
+               subject.attributes = subject2
+               assert subject.length == 0
+               subject2 = sample
+               subject.attributes = subject2
+               assert subject.length == 4
+       end
+
+       fun test_set do
+               var subject: AttributesImpl = sample
+
+               subject.set(1, "urn:is:not:often:used", "i-am_ME", "i-am_ME", "ID",
+                               "noop")
+               assert "i-am_ME" == subject.qname(1)
+               subject.set(0, "http://example.com/", "bar", "foo:bar", "NMTOKENS", "baz")
+               assert "NMTOKENS" == subject.type_of(0)
+       end
+
+       fun test_remove_at do
+               var subject: AttributesImpl = sample
+
+               subject.remove_at(1)
+               assert 3 == subject.length
+               assert "xml:lang" == subject.qname(1)
+       end
+
+       fun test_uri_set do
+               var subject: AttributesImpl = sample
+
+               subject.uri(0) = "https://example.org/serious"
+               subject.uri(1) = "ftp://wat"
+               assert "ftp://wat" == subject.uri(1)
+               assert "https://example.org/serious" == subject.uri(0)
+       end
+
+       fun test_local_name_set do
+               var subject: AttributesImpl = sample
+
+               subject.local_name(0) = "trololol"
+               subject.local_name(1) = "ImYou42"
+               assert "trololol" == subject.local_name(0)
+               assert "ImYou42" == subject.local_name(1)
+       end
+
+       fun test_qname_set do
+               var subject: AttributesImpl = sample
+
+               subject.qname(0) = "go-to:bar"
+               subject.qname(1) = "yo:i-am_ME"
+               assert "go-to:bar" == subject.qname(0)
+               assert "yo:i-am_ME" == subject.qname(1)
+       end
+
+       fun test_type_of_set do
+               var subject: AttributesImpl = sample
+
+               subject.type_of(0) = "NMTOKENS"
+               subject.type_of(1) = "ENTITY"
+               assert "NMTOKENS" == subject.type_of(0)
+               assert "ENTITY" == subject.type_of(1)
+       end
+
+       fun test_value_of_set do
+               var subject: AttributesImpl = sample
+
+               subject.value_of(0) = "buz"
+               subject.value_of(1) = "bizzz"
+               assert "buz" == subject.value_of(0)
+               assert "bizzz" == subject.value_of(1)
+       end
+end
diff --git a/lib/sax/helpers/test_namespace_support.nit b/lib/sax/helpers/test_namespace_support.nit
new file mode 100644 (file)
index 0000000..5798c33
--- /dev/null
@@ -0,0 +1,183 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Test suite for `namespace_support`.
+module test_namespace_support is test_suite
+
+import test_suite
+import sax::helpers::namespace_support
+
+class TestNamespaceSupport
+       super TestSuite
+
+       private fun sample: NamespaceSupport do
+               var subject = new NamespaceSupport
+
+               subject.push_context
+               subject.declare_prefix("", "http://www.w3.org/1999/xhtml")
+               subject.declare_prefix("dc", "http://www.purl.org/dc#")
+               return subject
+       end
+
+       fun test_reset do
+               var subject = sample
+
+               subject.reset
+               assert subject.xmlns == subject.uri("xml")
+               assert subject.nsdecl == subject.uri("xmlns")
+               assert subject.prefix("http://www.w3.org/1999/xhtml") == null
+               assert 2 == subject.declared_prefixes.length
+               assert 2 == subject.prefixes.length
+       end
+
+       fun test_push_context_override_default do
+               var subject = sample
+
+               subject.push_context
+               subject.declare_prefix("", "this:is:a:test")
+               assert "this:is:a:test" == subject.uri("")
+               assert "http://www.purl.org/dc#" == subject.uri("dc")
+               assert subject.xmlns == subject.uri("xml")
+               assert subject.nsdecl == subject.uri("xmlns")
+               assert subject.prefix("http://www.w3.org/1999/xhtml") == null
+               assert 1 == subject.declared_prefixes.length
+               assert 3 == subject.prefixes.length
+       end
+
+       fun test_push_context_override_dc do
+               var subject = sample
+
+               subject.push_context
+               subject.declare_prefix("dc", "this:is:a:test")
+               assert "this:is:a:test" == subject.uri("dc")
+               assert "http://www.w3.org/1999/xhtml" == subject.uri("")
+               assert subject.xmlns == subject.uri("xml")
+               assert subject.prefix("http://www.purl.org/dc#") == null
+               assert 1 == subject.declared_prefixes.length
+               assert 3 == subject.prefixes.length
+       end
+
+       fun test_push_context_undeclare do
+               var subject = sample
+
+               subject.push_context
+               subject.declare_prefix("dc", "")
+               assert subject.uri("dc") == null
+               assert 1 == subject.declared_prefixes.length
+               assert 2 == subject.prefixes.length
+       end
+
+       fun test_pop_context do
+               var subject = sample
+
+               subject.pop_context
+               assert subject.xmlns == subject.uri("xml")
+               assert subject.prefix("http://www.w3.org/1999/xhtml") == null
+               assert 2 == subject.declared_prefixes.length
+               assert 2 == subject.prefixes.length
+       end
+
+       #fun test_declare_prefix # SEE: test_push_context_*
+
+       fun test_process_name do
+               var subject = sample
+               var parts = new Array[String]
+
+               assert ["http://www.w3.org/1999/xhtml", "p", "p"] == subject.process_name("p", parts, false)
+               assert ["", "p", "p"] == subject.process_name("p", parts, true)
+               assert ["http://www.purl.org/dc#", "title", "dc:title"] == subject.process_name("dc:title", parts, false)
+               assert ["http://www.purl.org/dc#", "title", "dc:title"] == subject.process_name("dc:title", parts, true)
+               assert [subject.xmlns, "lang", "xml:lang"] == subject.process_name("xml:lang", parts, false)
+               assert ["http://www.w3.org/1999/xhtml", "p", ":p"] == subject.process_name(":p", parts, false)
+               assert ["", "p", ":p"] == subject.process_name(":p", parts, true)
+               assert subject.process_name("foo:bar", parts, false) == null
+               assert subject.process_name("foo:bar", parts, true) == null
+               subject.pop_context
+               assert ["", "p", "p"] == subject.process_name("p", parts, false)
+               assert ["", "p", "p"] == subject.process_name("p", parts, true)
+       end
+
+       fun test_process_name_xmlns do
+               var subject = sample
+               var parts = new Array[String].with_capacity(3)
+
+               assert [subject.nsdecl, "", "xmlns"] == subject.process_name("xmlns", parts, true)
+               assert ["http://www.w3.org/1999/xhtml", "xmlns", "xmlns"] == subject.process_name("xmlns", parts, false)
+       end
+
+       fun test_declare_prefix_illegal do
+               var subject = sample
+
+               assert not subject.declare_prefix("xml", "http://example.org")
+               assert not subject.declare_prefix("xmlns", "http://www2.example.org")
+               assert not subject.declare_prefix("foo", subject.xmlns)
+               assert not subject.declare_prefix("bar", subject.nsdecl)
+               assert 2 == subject.declared_prefixes.length
+       end
+
+       fun test_uri do
+               var subject = sample
+
+               assert "http://www.w3.org/1999/xhtml" == subject.uri("")
+               assert "http://www.purl.org/dc#" == subject.uri("dc")
+               assert subject.xmlns == subject.uri("xml")
+               assert subject.uri("foo") == null
+       end
+
+       fun test_prefixes do
+               var subject = sample
+               var res = sample.prefixes
+
+               assert 3 == res.length else
+                       sys.stderr.write("Expected 3; got {res.length}.\n")
+               end
+               assert res.has("dc")
+               assert res.has("xml")
+               assert res.has("xmlns")
+       end
+
+       fun test_prefix do
+               var subject = sample
+
+               assert subject.prefix("http://www.w3.org/1999/xhtml") == null
+               assert "dc" == subject.prefix("http://www.purl.org/dc#")
+               assert "xml" == subject.prefix(subject.xmlns)
+               assert subject.prefix("https://example.org/serious") == null
+       end
+
+       fun test_prefixes_of do
+               var subject = sample
+               var res: Collection[String]
+
+               assert new Array[String] == subject.prefixes_of("http://www.w3.org/1999/xhtml")
+               assert ["dc"] == subject.prefixes_of("http://www.purl.org/dc#")
+               assert ["xml"] == subject.prefixes_of(subject.xmlns)
+               assert new Array[String] == subject.prefixes_of("https://example.org/serious")
+               subject.declare_prefix("html", "http://www.w3.org/1999/xhtml")
+               assert ["html"] == subject.prefixes_of("http://www.w3.org/1999/xhtml")
+               subject.declare_prefix("dc2", "http://www.purl.org/dc#")
+               res = subject.prefixes_of("http://www.purl.org/dc#")
+               assert 2 == res.length else
+                       sys.stderr.write("Expected 2; got {res.length}.\n")
+               end
+               assert res.has_all(["dc", "dc2"])
+       end
+
+       fun test_declared_prefixes do
+               var subject = sample
+               var res = sample.declared_prefixes
+
+               assert 2 == res.length else
+                       sys.stderr.write("Expected 2; got {res.length}.\n")
+               end
+               assert res.has("")
+               assert res.has("dc")
+       end
+end
diff --git a/lib/sax/helpers/xml_filter_impl.nit b/lib/sax/helpers/xml_filter_impl.nit
new file mode 100644 (file)
index 0000000..7bcd021
--- /dev/null
@@ -0,0 +1,370 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Base class for deriving an XML filter.
+module sax::helpers::xml_filter_impl
+
+import sax::xml_reader
+import sax::xml_filter
+import sax::input_source
+import sax::sax_locator
+import sax::attributes
+import sax::entity_resolver
+import sax::dtd_handler
+import sax::content_handler
+import sax::error_handler
+import sax::sax_parse_exception
+
+# Base class for deriving an XML filter.
+#
+# This class is designed to sit between an `XMLReader`
+# and the client application's event handlers. By default, it
+# does nothing but pass requests up to the reader and events
+# on to the handlers unmodified, but subclasses can override
+# specific methods to modify the event stream or the configuration
+# requests as they pass through.
+#
+# Note: The original source code and documentation of this class comes, in part,
+# from [SAX 2.0](http://www.saxproject.org).
+class XMLFilterImpl
+       super XMLFilter
+       super EntityResolver
+       super DTDHandler
+       super ContentHandler
+       super ErrorHandler
+
+       # XMLFilter
+
+       redef var parent: nullable XMLReader = null is writable
+
+       # XMLReader
+
+       redef var entity_resolver: nullable EntityResolver = null is writable
+       redef var dtd_handler: nullable DTDHandler = null is writable
+       redef var content_handler: nullable ContentHandler = null is writable
+       redef var error_handler: nullable ErrorHandler = null is writable
+
+
+       ############################################################################
+       # XMLReader
+
+       # Construct an empty XML filter, with no parent.
+       #
+       # This filter will have no parent: you must assign a parent
+       # before you start a parse or do any configuration with
+       # `feature=` or `property=`, unless you use this as
+       # a pure event consumer rather than as an `XMLReader`.
+       #
+       # SEE: `parent`
+       init do
+       end
+
+       # Construct an XML filter with the specified parent.
+       #
+       # SEE: `parent`
+       init with_parent(parent_reader: XMLReader) do
+               parent = parent_reader
+       end
+
+       redef fun feature_recognized(name: String): Bool do
+               if parent == null then
+                       return false
+               else
+                       return parent.feature_recognized(name)
+               end
+       end
+
+       redef fun feature_readable(name: String): Bool do
+               if parent == null then
+                       return false
+               else
+                       return parent.feature_readable(name)
+               end
+       end
+
+       redef fun feature_writable(name: String): Bool do
+               if parent == null then
+                       return false
+               else
+                       return parent.feature_writable(name)
+               end
+       end
+
+       # Look up the value of a feature.
+       #
+       # This will always fail if the parent is `null`.
+       #
+       # Parameters:
+       #
+       # * `name`: The feature name.
+       #
+       # Returns:
+       #
+       # The current value of the feature.
+       #
+       # SEE: `feature_recognized`
+       #
+       # SEE: `feature_readable`
+       redef fun feature(name: String): Bool do
+               assert sax_recognized: parent != null else
+                       sys.stderr.write("Feature: {name}\n")
+               end
+               return parent.feature(name)
+       end
+
+       # Set the value of a feature.
+       #
+       # This will always fail if the parent is `null`.
+       #
+       # Parameters:
+       #
+       # * `name`: feature name.
+       # * `value`: requested feature value.
+       #
+       # Returns:
+       #
+       # `true` if the feature is set; `false` if the feature can not be set given
+       # the current context.
+       #
+       # SEE: `feature_recognized`
+       #
+       # SEE: `feature_writable`
+       redef fun feature=(name: String, value: Bool) do
+               assert sax_recognized: parent != null else
+                       sys.stderr.write("Feature: {name}\n")
+               end
+               parent.feature(name) = value
+       end
+
+       redef fun property_recognized(name: String): Bool do
+               if parent == null then
+                       return false
+               else
+                       return parent.property_recognized(name)
+               end
+       end
+
+       redef fun property_readable(name: String): Bool do
+               if parent == null then
+                       return false
+               else
+                       return parent.property_readable(name)
+               end
+       end
+
+       redef fun property_writable(name: String): Bool do
+               if parent == null then
+                       return false
+               else
+                       return parent.property_writable(name)
+               end
+       end
+
+       # Look up the value of a property.
+       #
+       # Parameters:
+       #
+       # * `name`: The property name.
+       #
+       # Returns:
+       #
+       # The current value of the property.
+       #
+       # SEE: `property_recognized`
+       #
+       # SEE: `property_readable`
+       redef fun property(name: String): nullable Object do
+               assert sax_recognized: parent != null else
+                       sys.stderr.write("Property: {name}\n")
+               end
+               return parent.property(name)
+       end
+
+       # Set the value of a property.
+       #
+       # This will always fail if the parent is `null`.
+       #
+       # Parameters:
+       #
+       # * `name`: property name.
+       # * `value`: requested feature value.
+       #
+       # Returns:
+       #
+       # `true` if the property is set; `false` if the property can not be set
+       # given the current context.
+       #
+       # SEE: `property_recognized`
+       #
+       # SEE: `property_writable`
+       redef fun property=(name: String, value: nullable Object) do
+               assert sax_recognized: parent != null else
+                       sys.stderr.write("Property: {name}\n")
+               end
+               parent.property(name) = value
+       end
+
+       redef fun parse(input: InputSource) do
+               setup_parse
+               parent.parse(input)
+       end
+
+       redef fun parse_file(system_id: String) do
+               var source = new InputSource
+
+               source.system_id = system_id
+               parse(source)
+       end
+
+
+       ############################################################################
+       # EntityResolver
+
+       redef fun resolve_entity(public_id: nullable String,
+                       system_id: nullable String):
+                       nullable InputSource do
+               if entity_resolver == null then
+                       return null
+               else
+                       return entity_resolver.resolve_entity(public_id, system_id)
+               end
+       end
+
+
+       ############################################################################
+       # DTDHandler
+
+       redef fun notation_decl(name: String, public_id: String,
+                       system_id: String) do
+               if dtd_handler != null then
+                       dtd_handler.notation_decl(name, public_id, system_id)
+               end
+       end
+
+       redef fun unparsed_entity_decl(name: String, public_id: String,
+                       system_id: String) do
+               if dtd_handler != null then
+                       dtd_handler.unparsed_entity_decl(name, public_id, system_id)
+               end
+       end
+
+
+       ############################################################################
+       # ContentHandler
+
+       redef fun document_locator=(locator: SAXLocator) do
+               if content_handler != null then
+                       content_handler.document_locator = locator
+               end
+       end
+
+       redef fun start_document do
+               if content_handler != null then
+                       content_handler.start_document
+               end
+       end
+
+       redef fun end_document do
+               if content_handler != null then
+                       content_handler.end_document
+               end
+       end
+
+       redef fun start_prefix_mapping(prefix: String, uri: String) do
+               if content_handler != null then
+                       content_handler.start_prefix_mapping(prefix, uri)
+               end
+       end
+
+       redef fun end_prefix_mapping(prefix: String) do
+               if content_handler != null then
+                       content_handler.end_prefix_mapping(prefix)
+               end
+       end
+
+       redef fun start_element(uri: String, local_name: String, qname: String,
+                       atts: Attributes) do
+               if content_handler != null then
+                       content_handler.start_element(uri, local_name, qname, atts)
+               end
+       end
+
+       redef fun end_element(uri: String, local_name: String, qname: String) do
+               if content_handler != null then
+                       content_handler.end_element(uri, local_name, qname)
+               end
+       end
+
+       redef fun characters(str: String) do
+               if content_handler != null then
+                       content_handler.characters(str)
+               end
+       end
+
+       redef fun ignorable_whitespace(str: String) do
+               if content_handler != null then
+                       content_handler.ignorable_whitespace(str)
+               end
+       end
+
+       redef fun processing_instruction(target: String, data: nullable String) do
+               if content_handler != null then
+                       content_handler.processing_instruction(target, data)
+               end
+       end
+
+       redef fun skipped_entity(name: String) do
+               if content_handler != null then
+                       content_handler.skipped_entity(name)
+               end
+       end
+
+
+       ############################################################################
+       # ErrorHandler
+
+       redef fun warning(exception: SAXParseException) do
+               if error_handler != null then
+                       error_handler.warning(exception)
+               end
+       end
+
+       redef fun error(exception: SAXParseException) do
+               if error_handler != null then
+                       error_handler.error(exception)
+               end
+       end
+
+       redef fun fatal_error(exception: SAXParseException) do
+               if error_handler != null then
+                       error_handler.fatal_error(exception)
+               else
+                       exception.throw
+               end
+       end
+
+       ############################################################################
+       # private
+
+       # Set up before a parse.
+       #
+       # Before every parse, check whether the parent is
+       # non-null, and re-register the filter for all of the
+       # events.
+       private fun setup_parse do
+               assert parent_is_not_null: parent != 0 else
+                       sys.stderr.write("No parent for filter.")
+               end
+               parent.entity_resolver = self
+               parent.dtd_handler = self
+               parent.content_handler = self
+               parent.error_handler = self
+       end
+end
diff --git a/lib/sax/input_source.nit b/lib/sax/input_source.nit
new file mode 100644 (file)
index 0000000..b3a27d8
--- /dev/null
@@ -0,0 +1,97 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# A single input source for an XML entity.
+module sax::input_source
+
+# A single input source for an XML entity.
+#
+# This class allows a SAX application to encapsulate information
+# about an input source in a single object, which may include
+# a public identifier, a system identifier and a stream (possibly
+# with a specified encoding).
+#
+# There are two places that the application can deliver an
+# input source to the parser: as the argument to the `XMLReader.parse`
+# method, or as the return value of the `EntityResolver.resolve_entity`
+# method.
+#
+# The SAX parser will use the `InputSource` object to determine how to read XML
+# input. If there is a byte stream, the parser will use that byte stream, using
+# the encoding specified in the InputSource or else (if no encoding is
+# specified) autodetecting the character encoding using an algorithm
+# such as the one in the XML specification. If no byte stream is available, the
+# parser will attempt to open a URI connection to the resource identified by
+# the system identifier.
+#
+# An InputSource object belongs to the application: the SAX parser
+# shall never modify it in any way (it may modify a copy if
+# necessary). However, standard processing of the stream is to close it on as
+# part of end-of-parse cleanup, so applications should not attempt to re-use
+# such streams after they have been handed to a parser.
+#
+# Note: The original documentation comes, in part,
+# from [SAX 2.0](http://www.saxproject.org).
+class InputSource
+
+       init do end
+
+       # Create a new input source with the specified system identifier.
+       #
+       # Applications may use `public_id=` to include a public identifier as well,
+       # or `encoding=` to specify the character encoding, if known.
+       #
+       # If the system identifier is a URL, it must be fully resolved (it may not
+       # be a relative URL).
+       init with_system_id(system_id: String) do
+               self.system_id = system_id
+       end
+
+       # Create a new input source with the specified stream.
+       #
+       # Application writers should use `system_id=` to provide a base for
+       # resolving relative URIs, may use `public_id=` to include a public
+       # identifier, and may use `encoding=` to specify the object's character
+       # encoding.
+       init with_stream(stream: IStream) do
+               self.stream = stream
+       end
+
+       # The public identifier as a string.
+       #
+       # The public identifier is always optional: if the application
+       # writer includes one, it will be provided as part of the
+       # location information.
+       var public_id: nullable String = null is writable
+
+       # The system identifier as a string.
+       #
+       # If its an URL, it must be fully resolved (it may not be a relative URL).
+       #
+       # Applications may set `public_id` to include a
+       # public identifier as well, or set `encoding` to specify
+       # the character encoding, if known.
+       var system_id: nullable String = null is writable
+
+       # The stream containing the document.
+       #
+       # Application writers should set `system_id` to provide a base
+       # for resolving relative URIs, may set `public_id` to include a
+       # public identifier, and may set `encoding` to specify the object's
+       # character encoding.
+       var stream: nullable IStream = null is writable
+
+       # The character encoding, if known.
+       #
+       # The encoding must be a string acceptable for an
+       # XML encoding declaration (see section 4.3.3 of the XML 1.0
+       # recommendation).
+       var encoding: nullable String = null is writable
+end
diff --git a/lib/sax/sax.nit b/lib/sax/sax.nit
new file mode 100644 (file)
index 0000000..c655ab3
--- /dev/null
@@ -0,0 +1,224 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Core SAX APIs.
+#
+# This is a (partial) port of the original SAX API. See
+# [http://www.saxproject.org](http://www.saxproject.org) for more information
+# about SAX.
+#
+# SAX2 Standard Feature Flags:
+#
+# One of the essential characteristics of SAX2 is that it added
+# feature flags which can be used to examine and perhaps modify
+# parser modes, in particular modes such as validation.
+# Since features are identified by (absolute) URIs, anyone
+# can define such features.
+#
+# For default values not specified by SAX2,
+# each `XMLReader` implementation specifies its default,
+# or may choose not to expose the feature flag.
+# Unless otherwise specified here,
+# implementations may support changing current values
+# of these standard feature flags, but not while parsing.
+#
+# Currently defined standard feature URIs have the prefix
+# `http://xml.org/sax/features/` before an identifier such as
+# `validation`. Support for the default values of the
+# `namespaces` and `namespace-prefixes`
+# properties is required. Turn features on or off using
+# `feature=`. Those standard identifiers are:
+#
+# `external-general-entities`:
+#
+# * Access: read/write
+# * Default: unspecified
+# * Description: Reports whether this parser processes external general
+# entities; always `true` if validating.
+#
+# `external-parameter-entities`:
+#
+# * Access: read/write
+# * Default: unspecified
+# * Description: Reports whether this parser processes external parameter
+# entities; always `true` if validating.
+#
+# `is-standalone`:
+#
+# * Access: read-only while parsing, none while not parsing
+# * Default: not applicable
+# * Description: May be examined only during a parse, after the `start_document`
+# callback has been completed; read-only. The value is `true` if the document
+# specified `standalone="yes"` in its XML declaration, and otherwise is `false`.
+#
+# `lexical-handler/parameter-entities`:
+#
+# * Access: read/write
+# * Default: unspecified
+# * Description: `true` indicates that the LexicalHandler will report the
+# beginning and end of parameter entities.
+#
+# `namespaces`:
+#
+# * Access: read/write
+# * Default: `true`
+# * Description: `true` indicates namespace URIs and unprefixed local names
+# for element and attribute names will be available.
+#
+# `namespace-prefixes`:
+#
+# * Access: read/write
+# * Default: `false`
+# * Description: `true` indicates XML 1.0 names (with prefixes) and attributes
+# (including `xmlns*` attributes) will be available.
+#
+# `resolve-dtd-uris`:
+#
+# * Access: read/write
+# * Default: `true`
+# * Description: `true` indicates that system IDs in declarations will be
+# absolutized (relative to their base URIs) before reporting. (That is the
+# default behavior for all SAX2 XML parsers.) A value of "false" indicates those
+# IDs will not be absolutized; parsers will provide the base URI from
+# `SAXLocator.system_id`. This applies to system IDs passed in
+# `DTDHandler.notation_decl`, DTDHandler.unparsed_entity_decl`, and
+# `DeclHandler.external_entity_decl`. It does not apply to
+# `EntityResolver.resolve_entity`, which is not used to report declarations, or
+# to `LexicalHandler.start_dtd`, which already provides the non-absolutized URI.
+#
+# `string-interning`:
+#
+# * Access: read-only
+# * Default: `false`
+# * Description: (Java-specific. Not supported in Nit.)
+#
+# `unicode-normalization-checking`:
+#
+# * Access: read/write
+# * Default: `false`
+# * Description: Controls whether the parser reports Unicode normalization
+# errors as described in section 2.13 and Appendix B of the XML 1.1
+# Recommendation. If `true`, Unicode normalization errors are reported using
+# the `ErrorHandler.error` callback. Such errors are not fatal in themselves
+# (though, obviously, other Unicode-related encoding errors may be).
+#
+# `use-attributes2`:
+#
+# * Access: read-only
+# * Default: not applicable
+# * Description: Returns `true` if the `Attributes` objects passed by this
+# parser in `ContentHandler.start_element` implement the
+# `sax::ext::Attributes2` interface. That interface exposes additional
+# DTD-related information, such as whether the attribute was specified in the
+# source text rather than defaulted.
+#
+# TODO: interface not yet available in Nit.
+#
+# `use-locator2`:
+#
+# * Access: read-only
+# * Default: not applicable
+# * Description: Returns `true` if the `SAXLocator` objects passed by this
+# parser in `ContentHandler.document_locator=` implement the
+# `sax::ext::SAXLocator2` interface. That interface exposes additional
+# entity information, such as the character encoding and XML version used.
+#
+# TODO: interface not yet available in Nit.
+#
+# `use-entity-resolver2`:
+#
+# * Access: read/write
+# * Default: true
+# * Description: Returns `true` if, when `entity_resolver` is given an object
+# implementing the `sax::ext::EntityResolver2` interface, those new methods
+# will be used. Returns `false` to indicate that those methods will not be used.
+#
+# `validation`:
+#
+# * Access: read/write
+# * Default: unspecified
+# * Description: controls whether the parser is reporting all validity errors;
+# if true, all external entities will be read.
+#
+# `xmlns-uris`:
+#
+# * Access: read/write
+# * Default: `false`
+# * Description: Controls whether, when the `namespace-prefixes` feature is set,
+# the parser treats namespace declaration attributes as being in the
+# `http://www.w3.org/2000/xmlns/` namespace. By default, SAX2 conforms to the
+# original "Namespaces in XML" Recommendation, which explicitly states that such
+# attributes are not in any namespace. Setting this optional flag to `true`
+# makes the SAX2 events conform to a later backwards-incompatible revision of
+# that recommendation, placing those attributes in a namespace.
+#
+# `xml-1.1`:
+#
+# * Access: read-only
+# * Default: not applicable
+# * Description: Returns `true` if the parser supports both XML 1.1 and XML 1.0.
+# Returns "false" if the parser supports only XML 1.0.
+#
+#
+# SAX2 Standard Handler and Property IDs:
+#
+# For parser interface characteristics that are described as objects, a
+# separate namespace is defined. The objects in this namespace are again
+# identified by URI, and the standard property URIs have the prefix
+# `http://xml.org/sax/properties/` before an identifier such as
+# `lexical-handler` or `dom-node`. All of these standard properties are
+# optional; `XMLReader` implementations need not support them. Manage those
+# properties using `property=`. Those identifiers are:
+#
+# `declaration-handler`:
+#
+# Used to see most DTD declarations except those treated as lexical (“document
+# element name is...”) or which are mandatory for all SAX parsers
+# (`DTDHandler`). The object must implement `sax::ext::DeclHandler`.
+#
+# `document-xml-version`:
+#
+# May be examined only during a parse, after the `start_document` callback
+# has been completed; read-only. This property is a literal string describing
+# the actual XML version of the document, such as `"1.0"` or `"1.1"`.
+#
+# `dom-node`:
+#
+# For “DOM Walker” style parsers, which ignore their `parser.parse` parameters,
+# this is used to specify the DOM (sub)tree being walked by the parser. The
+# object must implement the `xml.dom.Node` interface.
+#
+# TODO: interface not yet available in Nit.
+#
+# `lexical-handler`:
+#
+# Used to see some syntax events that are essential in some applications:
+# comments, `CDATA` delimeters, selected general entity inclusions, and the
+# start and end of the DTD (and declaration of document element name).
+# The object must implement `sax::ext::LexicalHandler`.
+#
+# `xml-string`:
+#
+# Readable only during a parser callback, this exposes a **TBS** chunk of
+# characters responsible for the current event.
+#
+# Note: The original documentation comes from [SAX 2.0](http://www.saxproject.org).
+module sax
+
+import attributes
+import content_handler
+import dtd_handler
+import entity_resolver
+import error_handler
+import input_source
+import sax_locator
+import sax_parse_exception
+import xml_filter
+import xml_reader
diff --git a/lib/sax/sax_locator.nit b/lib/sax/sax_locator.nit
new file mode 100644 (file)
index 0000000..746ec2c
--- /dev/null
@@ -0,0 +1,107 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Interface for associating a SAX event with a document location.
+module sax::sax_locator
+
+# Interface for associating a SAX event with a document location.
+#
+# If a SAX parser provides location information to the SAX
+# application, it does so by implementing this interface and then
+# passing an instance to the application using the
+# `ContentHandler.document_locator=` method. The application can use
+# the object to obtain the location of any other SAX event
+# in the XML source document.
+#
+# Note that the results returned by the object will be valid only
+# during the scope of each callback method: the application
+# will receive unpredictable results if it attempts to use the
+# locator at any other time, or after parsing completes.
+#
+# SAX parsers are not required to supply a locator, but they are
+# very strongly encouraged to do so. If the parser supplies a
+# locator, it must do so before reporting any other document events.
+# If no locator has been set by the time the application receives
+# the `ContentHandler.start_document` event, the application should
+# assume that a locator is not available.
+#
+# Note: The original documentation comes from [SAX 2.0](http://www.saxproject.org).
+#
+# SEE: `sax::ContentHandler.document_locator`
+public interface SAXLocator
+
+       # Return the public identifier for the current document event.
+       #
+       # Return the public identifier of the document
+       # entity or of the external parsed entity in which the markup
+       # triggering the event appears.
+       # Return `null` if no public identifier is available.
+       fun public_id: nullable String is abstract
+
+       # Return the system identifier for the current document event.
+       #
+       # Return the system identifier of the document
+       # entity or of the external parsed entity in which the markup
+       # triggering the event appears.
+       # Return `null` if no system identifier is available.
+       #
+       # If the system identifier is a URL, the parser must resolve it
+       # fully before passing it to the application. For example, a file
+       # name must always be provided as a `file:` URL, and other
+       # kinds of relative URI are also resolved against their bases.
+       fun system_id: nullable String is abstract
+
+       # Return the line number where the current document event ends.
+       #
+       # Lines are delimited by line ends, which are defined in
+       # the XML specification.
+       #
+       # **Warning:** The return value from this method
+       # is intended only as an approximation for the sake of diagnostics;
+       # it is not intended to provide sufficient information
+       # to edit the character content of the original XML document.
+       # In some cases, these "line" numbers match what would be displayed
+       # as columns, and in others they may not match the source text
+       # due to internal entity expansion.
+       #
+       # Return an approximation of the line number
+       # in the document entity or external parsed entity where the
+       # markup triggering the event appears.
+       #
+       # If possible, the SAX driver should provide the line position
+       # of the first character after the text associated with the document
+       # event. The first line is line 1.
+       #
+       # Return -1 in absence of line number.
+       fun line_number: Int is abstract
+
+       # Return the column number where the current document event ends.
+       #
+       # The number is one-based.
+       #
+       # **Warning:** The return value from this method
+       # is intended only as an approximation for the sake of diagnostics;
+       # it is not intended to provide sufficient information
+       # to edit the character content of the original XML document.
+       # For example, when lines contain combining character sequences, wide
+       # characters, surrogate pairs, or bi-directional text, the value may
+       # not correspond to the column in a text editor's display.
+       #
+       # The return value is an approximation of the column number
+       # in the document entity or external parsed entity where the
+       # markup triggering the event appears.
+       #
+       # If possible, the SAX driver should provide the line position
+       # of the first character after the text associated with the document
+       # event. The first column in each line is column 1.
+       #
+       # Return -1 in absence of column number.
+       fun column_number: Int is abstract
+end
diff --git a/lib/sax/sax_parse_exception.nit b/lib/sax/sax_parse_exception.nit
new file mode 100644 (file)
index 0000000..ae2586b
--- /dev/null
@@ -0,0 +1,136 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Encapsulates an XML parse error or warning.
+module sax::sax_parse_exception
+
+import sax_locator
+
+# Encapsulates an XML parse error or warning.
+#
+# This exception may include information for locating the error
+# in the original XML document, as if it came from a `Locator`
+# object. Note that although the application
+# will receive a `SAXParseException` as the argument to the handlers
+# in the `ErrorHandler` interface,
+# the application is not actually required to throw the exception;
+# instead, it can simply read the information in it and take a
+# different action.
+#
+# Note: The original documentation comes, in part,
+# from [SAX 2.0](http://www.saxproject.org).
+#
+# SEE: `sax::SAXLocator`
+#
+# SEE: `sax::ErrorHandler`
+class SAXParseException
+       super Error
+
+       # The public identifer of the entity that generated
+       # the error or warning.
+       var public_id: nullable String = null
+
+       # The system identifer of the entity that generated
+       # the error or warning.
+       #
+       # If its an URL, it must be fully resolved.
+       var system_id: nullable String = null
+
+       # The line number of the end of the text that
+       # caused the error or warning, or -1.
+       var line_number: Int = -1
+
+       # The column number of the end of the text that
+       # caused the error or warning, or -1.
+       var column_number: Int = -1
+
+       # Create a new SAXParseException from a message and a Locator.
+       #
+       # This constructor is especially useful when an application is
+       # creating its own exception from within a `ContentHandler` callback.
+       #
+       # Parameters:
+       #
+       # * `message`: error or warning message.
+       # * `locator`: locator object for the error or warning.
+       init with_locator(message: String, locator: SAXLocator) do
+               init(message)
+               public_id = locator.public_id
+               system_id = locator.system_id
+               line_number = locator.line_number
+               column_number = locator.column_number
+       end
+
+       # Create a new SAXParseException.
+       #
+       # This constructor is most useful for parser writers.
+       #
+       # All parameters except the message are as if they were provided by a
+       # `Locator`. For example, if the system identifier is a URL (including
+       # relative filename), the caller must resolve it fully before creating the
+       # exception.
+       #
+       # Parameters:
+       #
+       # * `message`: error or warning message.
+       # * `public_id`: public identifer of the entity that generated
+       # the error or warning.
+       # * `system_id`: system identifer of the entity that generated
+       # the error or warning.
+       # * `line_number`: line number of the end of the text that
+       # caused the error or warning.
+       # * `column_number`: column number of the end of the text that
+       # caused the error or warning.
+       init with(message: String, public_id: nullable String,
+                       system_id: nullable String, line_number: Int, column_number: Int) do
+               init(message)
+               self.public_id = public_id
+               self.system_id = system_id
+               self.line_number = line_number
+               self.column_number = column_number
+       end
+
+       # Generate a complete message from the exception’s attributes.
+       fun full_message: String do
+               var location = ""
+
+               if public_id != null then
+                       location = "PUBLIC \"{public_id.as(not null)}\""
+               end
+               if system_id != null then
+                       if location != "" then
+                               location += " "
+                       end
+                       location += "SYSTEM \"{system_id.as(not null)}\""
+               end
+               if line_number >= 0 or column_number >= 0 then
+                       if location != "" then
+                               location += " at "
+                       end
+                       location += "{line_number};{column_number}"
+               end
+               if location == "" then
+                       return "{message}"
+               else
+                       return "[{location}] {message}"
+               end
+       end
+
+       redef fun to_s do
+               return "sax::SAXParseException: {full_message}"
+       end
+
+       # Display `full_message` with a stack trace, then abort.
+       fun throw do
+               assert sax_parse: false else
+                       sys.stderr.write "{full_message}\n"
+               end
+       end
+end
diff --git a/lib/sax/xml_filter.nit b/lib/sax/xml_filter.nit
new file mode 100644 (file)
index 0000000..9c12c48
--- /dev/null
@@ -0,0 +1,47 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Interface for an XML filter.
+module sax::xml_filter
+
+import xml_reader
+
+# Interface for an XML filter.
+#
+# An XML filter is like an XML reader, except that it obtains its
+# events from another XML reader rather than a primary source like
+# an XML document or database. Filters can modify a stream of
+# events as they pass on to the final application.
+#
+# The `XMLFilterImpl` helper class provides a convenient base
+# for creating SAX2 filters, by passing on all `EntityResolver`,
+# `DTDHandler`, `ContentHandler` and `ErrorHandler` events automatically.
+#
+# Note: The original documentation comes from [SAX 2.0](http://www.saxproject.org).
+#
+# SEE: `sax::helpers::XMLFilterImpl`
+interface XMLFilter super XMLReader
+
+       # Set the parent reader.
+       #
+       # This method allows the application to link the filter to
+       # a parent reader (which may be another filter).
+       fun parent=(parent: nullable XMLReader) is abstract
+
+       # Get the parent reader.
+       #
+       # This method allows the application to query the parent
+       # reader (which may be another filter). It is generally a
+       # bad idea to perform any operations on the parent reader
+       # directly: they should all pass through this filter.
+       #
+       # Return null if no parent has been set.
+       fun parent: nullable XMLReader is abstract
+end
diff --git a/lib/sax/xml_reader.nit b/lib/sax/xml_reader.nit
new file mode 100644 (file)
index 0000000..c158532
--- /dev/null
@@ -0,0 +1,289 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Interface for reading an XML document using callbacks.
+module sax::xml_reader
+
+import entity_resolver
+import dtd_handler
+import content_handler
+import error_handler
+
+# Interface for reading an XML document using callbacks.
+#
+# `XMLReader` is the interface that an XML parser's SAX2 driver must
+# implement. This interface allows an application to set and
+# query features and properties in the parser, to register
+# event handlers for document processing, and to initiate
+# a document parse.
+#
+# All SAX interfaces are assumed to be synchronous: the
+# `parse` methods must not return until parsing
+# is complete, and readers must wait for an event-handler callback
+# to return before reporting the next event.
+#
+# Note: The original documentation comes from [SAX 2.0](http://www.saxproject.org).
+interface XMLReader
+
+       # Is the specified feature flag recognized by this parser?
+       #
+       # Parameter:
+       #
+       # * `name`: feature name, which is a fully-qualified URI.
+       fun feature_recognized(name: String): Bool is abstract
+
+       # Is the retrieval of the specified feature flag supported given the current context?
+       #
+       # Parameter:
+       #
+       # * `name`: feature name, which is a fully-qualified URI.
+       fun feature_readable(name: String): Bool is abstract
+
+       # Is the modification of the specified feature flag supported given the current context?
+       #
+       # Parameter:
+       #
+       # * `name`: feature name, which is a fully-qualified URI.
+       fun feature_writable(name: String): Bool is abstract
+
+       # Look up the value of a feature flag.
+       #
+       # The feature name is any fully-qualified URI. It is
+       # possible for an `XMLReader` to recognize a feature name but
+       # temporarily be unable to return its value.
+       # Some feature values may be available only in specific
+       # contexts, such as before, during, or after a parse.
+       # Also, some feature values may not be programmatically accessible.
+       #
+       # All XMLReaders are required to recognize the
+       # `http://xml.org/sax/features/namespaces` and the
+       # `http://xml.org/sax/features/namespace-prefixes` feature names.
+       #
+       # Implementors are free (and encouraged) to invent their own features,
+       # using names built on their own URIs.
+       #
+       # Parameter:
+       #
+       # * `name`: feature name, which is a fully-qualified URI.
+       #
+       # Returns:
+       #
+       # The current value of the feature.
+       #
+       # SEE: `feature_recognized`
+       #
+       # SEE: `feature_readable`
+       fun feature(name: String): Bool is abstract
+
+       # Set the value of a feature flag.
+       #
+       # The feature name is any fully-qualified URI. It is
+       # possible for an XMLReader to expose a feature value but
+       # to be unable to change the current value.
+       # Some feature values may be immutable or mutable only
+       # in specific contexts, such as before, during, or after
+       # a parse.
+       #
+       # All XMLReaders are required to support setting
+       # http://xml.org/sax/features/namespaces to true and
+       # http://xml.org/sax/features/namespace-prefixes to false.
+       #
+       # Parameters:
+       #
+       # * `name`: feature name, which is a fully-qualified URI.
+       # * `value`: requested value of the feature.
+       #
+       # SEE: `feature_recognized`
+       #
+       # SEE: `feature_writable`
+       fun feature=(name: String, value: Bool) is abstract
+
+       # Is the specified property recognized by this parser?
+       #
+       # Parameter:
+       #
+       # * `name`: property name, which is a fully-qualified URI.
+       fun property_recognized(name: String): Bool is abstract
+
+       # Is the retrieval of the specified property supported given the current context?
+       #
+       # Parameter:
+       #
+       # * `name`: property name, which is a fully-qualified URI.
+       fun property_readable(name: String): Bool is abstract
+
+       # Is the modification of the specified property supported given the current context?
+       #
+       # Parameter:
+       #
+       # * `name`: property name, which is a fully-qualified URI.
+       fun property_writable(name: String): Bool is abstract
+
+       # Look up the value of a property.
+       #
+       # The property name is any fully-qualified URI. It is
+       # possible for an `XMLReader` to recognize a property name but
+       # temporarily be unable to return its value.
+       # Some property values may be available only in specific
+       # contexts, such as before, during, or after a parse.
+       #
+       # XMLReaders are not required to recognize any specific
+       # property names, though an initial core set is documented for
+       # SAX2.
+       #
+       # Implementors are free (and encouraged) to invent their own properties,
+       # using names built on their own URIs.
+       #
+       # Parameter:
+       #
+       # * `name`: property name, which is a fully-qualified URI.
+       #
+       # Returns:
+       #
+       # The current value of the property.
+       #
+       # SEE: `property_recognized`
+       #
+       # SEE: `property_supported`
+       fun property(name: String): nullable Object is abstract
+
+       # Set the value of a property.
+       #
+       # The property name is any fully-qualified URI. It is
+       # possible for an `XMLReader` to recognize a property name but
+       # to be unable to change the current value.
+       # Some property values may be immutable or mutable only
+       # in specific contexts, such as before, during, or after
+       # a parse.
+       #
+       # XMLReaders are not required to recognize setting
+       # any specific property names, though a core set is defined by
+       # SAX2.
+       #
+       # This method is also the standard mechanism for setting
+       # extended handlers.
+       #
+       # Parameters:
+       #
+       # * `name`: property name, which is a fully-qualified URI.
+       # * `value`: requested value for the property.
+       #
+       # SEE: `property_recognized`
+       #
+       # SEE: `property_writable`
+       fun property=(name: String, value: nullable Object) is abstract
+
+       # Allow an application to register an entity resolver.
+       #
+       # If the application does not register an entity resolver,
+       # the XMLReader will perform its own default resolution.
+       #
+       # Applications may register a new or different resolver in the
+       # middle of a parse, and the SAX parser must begin using the new
+       # resolver immediately.
+       fun entity_resolver=(resolver: nullable EntityResolver) is abstract
+
+       # Return the current entity resolver.
+       #
+       # Return `null` if none has been registered.
+       fun entity_resolver: nullable EntityResolver is abstract
+
+       # Allow an application to register a DTD event handler.
+       #
+       # If the application does not register a DTD handler, all DTD
+       # events reported by the SAX parser will be silently ignored.
+       #
+       # Applications may register a new or different handler in the
+       # middle of a parse, and the SAX parser must begin using the new
+       # handler immediately.
+       fun dtd_handler=(handler: nullable DTDHandler) is abstract
+
+       # Return the current DTD handler.
+       #
+       # Return `null` if none has been registered.
+       fun dtd_handler: nullable DTDHandler is abstract
+
+       # Allow an application to register a content event handler.
+       #
+       # If the application does not register a content handler, all
+       # content events reported by the SAX parser will be silently
+       # ignored.
+       #
+       # Applications may register a new or different handler in the
+       # middle of a parse, and the SAX parser must begin using the new
+       # handler immediately.
+       fun content_handler=(handler: nullable ContentHandler) is abstract
+
+       # Return the current content handler.
+       #
+       # Return `null` if none has been registered.
+       fun content_handler: nullable ContentHandler is abstract
+
+       # Allow an application to register an error event handler.
+       #
+       # If the application does not register an error handler, all
+       # error events reported by the SAX parser will be silently
+       # ignored; however, normal processing may not continue. It is
+       # highly recommended that all SAX applications implement an
+       # error handler to avoid unexpected bugs.
+       #
+       # Applications may register a new or different handler in the
+       # middle of a parse, and the SAX parser must begin using the new
+       # handler immediately.
+       fun error_handler=(handler: nullable ErrorHandler) is abstract
+
+       # Return the current error handler.
+       #
+       # Return `null` if none has been registered.
+       fun error_handler: nullable ErrorHandler is abstract
+
+       # Parse an XML document.
+       #
+       # The application can use this method to instruct the XML
+       # reader to begin parsing an XML document from any valid input
+       # source (a byte stream or an URI).
+       #
+       # Applications may not invoke this method while a parse is in
+       # progress (they should create a new `XMLReader` instead for each
+       # nested XML document). Once a parse is complete, an
+       # application may reuse the same `XMLReader` object, possibly with a
+       # different input source.
+       #
+       # During the parse, the `XMLReader` will provide information
+       # about the XML document through the registered event
+       # handlers.
+       #
+       # This method is synchronous: it will not return until parsing
+       # has ended. If a client application wants to terminate
+       # parsing early, it should throw an exception.
+       #
+       # Parameters:
+       #
+       # * `source`: input source for the top-level of the XML document.
+       fun parse(input: InputSource) is abstract
+
+       # Parse an XML document from a system identifier (URI).
+       #
+       # This method is a shortcut for the common case of reading a
+       # document from a system identifier. It is the exact
+       # equivalent of the following:
+       #
+       #     var source = new InputSouce
+       #     source.system_id = system_id
+       #     parse(source)
+       #
+       # If the system identifier is a URL, it must be fully resolved
+       # by the application before it is passed to the parser.
+       #
+       # Parameters:
+       #
+       # * `systemId`: The system identifier (URI).
+       fun parse_file(system_id: String) is abstract
+end