xml: Port SAX 2.0.
[nit.git] / lib / sax / helpers / attributes_impl.nit
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