Property definitions

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