sax :: Context :: defaultinit
# 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
var empty: Collection[String] = new Array[String].with_capacity(0)
# `prefix` -> `uri`
var prefix_table: nullable Map[String, String] = null
# Cache of `process_name` for elements.
#
# `qname -> [uri, local_name, qname]`
var element_name_table: nullable Map[String, Array[String]] = null
# Cache of `process_name` for attributes.
#
# `qname -> [uri, local_name, qname]`
var attribute_name_table: nullable Map[String, Array[String]] = null
# Namespace in absence of prefix.
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.
var declarations: nullable Array[String] = null
# Was `copy_tables` called since the last call to `parent=`?
var decl_seen: Bool = false
# Parent context.
var p_parent: nullable Context = null
# (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.
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.add_all(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
lib/sax/helpers/namespace_support.nit:387,1--662,3