Proper namespace support for elements.
This is still a bit rough on the edges but already way better than the broken setup I had before.
This commit is contained in:
parent
04cbbdcf9e
commit
33c28f633b
|
@ -8,47 +8,48 @@ module Oga
|
||||||
# The name of the element.
|
# The name of the element.
|
||||||
# @return [String]
|
# @return [String]
|
||||||
#
|
#
|
||||||
# @!attribute [rw] namespace
|
# @!attribute [ww] namespace_name
|
||||||
# The namespace of the element, if any.
|
# The name of the namespace.
|
||||||
# @return [Oga::XML::Namespace]
|
# @return [String]
|
||||||
#
|
#
|
||||||
# @!attribute [rw] attributes
|
# @!attribute [rw] attributes
|
||||||
# The attributes of the element.
|
# The attributes of the element.
|
||||||
# @return [Array<Oga::XML::Attribute>]
|
# @return [Array<Oga::XML::Attribute>]
|
||||||
#
|
#
|
||||||
|
# @!attribute [rw] namespaces
|
||||||
|
# The registered namespaces.
|
||||||
|
# @return [Hash]
|
||||||
|
#
|
||||||
class Element < Node
|
class Element < Node
|
||||||
attr_accessor :name, :namespace, :attributes
|
attr_accessor :name, :namespace_name, :attributes, :namespaces
|
||||||
|
|
||||||
##
|
##
|
||||||
# List of options that can be passed to the constructor and the required
|
# The attribute prefix/namespace used for registering element namespaces.
|
||||||
# types of their values.
|
|
||||||
#
|
#
|
||||||
# @return [Hash]
|
# @return [String]
|
||||||
#
|
#
|
||||||
OPTION_TYPES = {
|
XMLNS_PREFIX = 'xmlns'.freeze
|
||||||
:namespace => Namespace,
|
|
||||||
:attributes => Array
|
|
||||||
}
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# @param [Hash] options
|
# @param [Hash] options
|
||||||
#
|
#
|
||||||
# @option options [String] :name The name of the element.
|
# @option options [String] :name The name of the element.
|
||||||
#
|
#
|
||||||
# @option options [Oga::XML::Namespace] :namespace The namespace of the
|
# @option options [String] :namespace_name The name of the namespace.
|
||||||
# element.
|
|
||||||
#
|
#
|
||||||
# @option options [Array<Oga::XML::Attribute>] :attributes The attributes
|
# @option options [Array<Oga::XML::Attribute>] :attributes The attributes
|
||||||
# of the element as an Array.
|
# of the element as an Array.
|
||||||
#
|
#
|
||||||
def initialize(options = {})
|
def initialize(options = {})
|
||||||
validate_option_types!(options)
|
|
||||||
|
|
||||||
super
|
super
|
||||||
|
|
||||||
@name = options[:name]
|
@name = options[:name]
|
||||||
@namespace = options[:namespace]
|
@namespace_name = options[:namespace_name]
|
||||||
@attributes = options[:attributes] || []
|
@attributes = options[:attributes] || []
|
||||||
|
@namespaces = options[:namespaces] || {}
|
||||||
|
|
||||||
|
link_attributes
|
||||||
|
register_namespaces_from_attributes
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -76,6 +77,15 @@ module Oga
|
||||||
|
|
||||||
alias_method :attr, :attribute
|
alias_method :attr, :attribute
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns the namespace of the element.
|
||||||
|
#
|
||||||
|
# @return [Oga::XML::Namespace]
|
||||||
|
#
|
||||||
|
def namespace
|
||||||
|
return @namespace ||= available_namespaces[namespace_name]
|
||||||
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
# Returns the text of all child nodes joined together.
|
# Returns the text of all child nodes joined together.
|
||||||
#
|
#
|
||||||
|
@ -146,40 +156,63 @@ module Oga
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
# Returns a node set of all the namespaces that are available to the
|
# Registers a new namespace for the current element and its child
|
||||||
# current node. This includes the namespaces registered on the current
|
# elements.
|
||||||
# node.
|
|
||||||
#
|
#
|
||||||
# @return [Oga::XML::NodeSet]
|
# @param [String] name
|
||||||
|
# @param [String] uri
|
||||||
|
# @see [Oga::XML::Namespace#initialize]
|
||||||
#
|
#
|
||||||
def available_namespaces
|
def register_namespace(name, uri)
|
||||||
|
if namespaces[name]
|
||||||
|
raise ArgumentError, "The namespace #{name.inspect} already exists"
|
||||||
|
end
|
||||||
|
|
||||||
|
namespaces[name] = Namespace.new(:name => name, :uri => uri)
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
# Returns a node set of all the namespaces registered with the current
|
# Returns a Hash containing all the namespaces available to the current
|
||||||
# node.
|
# element.
|
||||||
#
|
#
|
||||||
# @return [Oga::XML::NodeSet]
|
# @return [Hash]
|
||||||
#
|
#
|
||||||
def namespaces
|
def available_namespaces
|
||||||
|
merged = namespaces
|
||||||
|
node = parent
|
||||||
|
|
||||||
|
while node && node.respond_to?(:namespaces)
|
||||||
|
merged = merged.merge(node.namespaces)
|
||||||
|
node = node.parent
|
||||||
|
end
|
||||||
|
|
||||||
|
return merged
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
##
|
##
|
||||||
# @param [Hash] options
|
# Registers namespaces based on any "xmlns" attributes. Once a namespace
|
||||||
# @raise [TypeError]
|
# has been registered the corresponding attribute is removed.
|
||||||
#
|
#
|
||||||
def validate_option_types!(options)
|
def register_namespaces_from_attributes
|
||||||
OPTION_TYPES.each do |key, type|
|
self.attributes = attributes.reject do |attr|
|
||||||
if options[key] and !options[key].is_a?(type)
|
# We're using `namespace_name` opposed to `namespace.name` as "xmlns"
|
||||||
raise(
|
# is not a registered namespace.
|
||||||
TypeError,
|
remove = attr.namespace_name && attr.namespace_name == XMLNS_PREFIX
|
||||||
"#{key.inspect} must be an instance of #{type}"
|
|
||||||
)
|
register_namespace(attr.name, attr.value) if remove
|
||||||
end
|
|
||||||
|
remove
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Links all attributes to the current element.
|
||||||
|
#
|
||||||
|
def link_attributes
|
||||||
|
attributes.each do |attr|
|
||||||
|
attr.element = self
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -206,7 +239,7 @@ module Oga
|
||||||
if ns
|
if ns
|
||||||
ns_matches = attr.namespace.to_s == ns
|
ns_matches = attr.namespace.to_s == ns
|
||||||
|
|
||||||
elsif name_matches
|
elsif name_matches and !attr.namespace
|
||||||
ns_matches = true
|
ns_matches = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -336,14 +336,10 @@ Unexpected #{name} with value #{value.inspect} on line #{@line}:
|
||||||
# @return [Oga::XML::Element]
|
# @return [Oga::XML::Element]
|
||||||
#
|
#
|
||||||
def on_element(namespace, name, attributes = {})
|
def on_element(namespace, name, attributes = {})
|
||||||
if namespace
|
|
||||||
namespace = Namespace.new(:name => namespace)
|
|
||||||
end
|
|
||||||
|
|
||||||
element = Element.new(
|
element = Element.new(
|
||||||
:namespace => namespace,
|
:namespace_name => namespace,
|
||||||
:name => name,
|
:name => name,
|
||||||
:attributes => attributes
|
:attributes => attributes
|
||||||
)
|
)
|
||||||
|
|
||||||
return element
|
return element
|
||||||
|
|
|
@ -6,18 +6,6 @@ describe Oga::XML::Element do
|
||||||
described_class.new(:name => 'p').name.should == 'p'
|
described_class.new(:name => 'p').name.should == 'p'
|
||||||
end
|
end
|
||||||
|
|
||||||
example 'raise TypeError when the namespace is not a Namespace' do
|
|
||||||
block = lambda { described_class.new(:namespace => 'x') }
|
|
||||||
|
|
||||||
block.should raise_error(TypeError)
|
|
||||||
end
|
|
||||||
|
|
||||||
example 'raise TypeError when the attributes are not an Array' do
|
|
||||||
block = lambda { described_class.new(:attributes => 'foo') }
|
|
||||||
|
|
||||||
block.should raise_error(TypeError)
|
|
||||||
end
|
|
||||||
|
|
||||||
example 'set the name via a setter' do
|
example 'set the name via a setter' do
|
||||||
instance = described_class.new
|
instance = described_class.new
|
||||||
instance.name = 'p'
|
instance.name = 'p'
|
||||||
|
@ -30,18 +18,42 @@ describe Oga::XML::Element do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'setting namespaces via attributes' do
|
||||||
|
before do
|
||||||
|
attr = Oga::XML::Attribute.new(:name => 'foo', :namespace_name => 'xmlns')
|
||||||
|
|
||||||
|
@element = described_class.new(:attributes => [attr])
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'register the "foo" namespace' do
|
||||||
|
@element.namespaces['foo'].is_a?(Oga::XML::Namespace).should == true
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'remove the namespace attribute from the list of attributes' do
|
||||||
|
@element.attributes.empty?.should == true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context '#attribute' do
|
context '#attribute' do
|
||||||
before do
|
before do
|
||||||
attributes = [
|
attributes = [
|
||||||
Oga::XML::Attribute.new(:name => 'key', :value => 'value'),
|
Oga::XML::Attribute.new(:name => 'key', :value => 'value'),
|
||||||
Oga::XML::Attribute.new(
|
Oga::XML::Attribute.new(
|
||||||
:name => 'key',
|
:name => 'bar',
|
||||||
:value => 'foo',
|
:value => 'baz',
|
||||||
:namespace => Oga::XML::Namespace.new(:name => 'x')
|
:namespace_name => 'x'
|
||||||
|
),
|
||||||
|
Oga::XML::Attribute.new(
|
||||||
|
:name => 'key',
|
||||||
|
:value => 'foo',
|
||||||
|
:namespace_name => 'x'
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@instance = described_class.new(:attributes => attributes)
|
@instance = described_class.new(
|
||||||
|
:attributes => attributes,
|
||||||
|
:namespaces => {'x' => Oga::XML::Namespace.new(:name => 'x')}
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
example 'return an attribute with only a name' do
|
example 'return an attribute with only a name' do
|
||||||
|
@ -71,6 +83,24 @@ describe Oga::XML::Element do
|
||||||
example 'return nil for a non existing attribute' do
|
example 'return nil for a non existing attribute' do
|
||||||
@instance.attribute('foobar').nil?.should == true
|
@instance.attribute('foobar').nil?.should == true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
example 'return nil if an attribute has a namespace that is not given' do
|
||||||
|
@instance.attribute('bar').nil?.should == true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context '#namespace' do
|
||||||
|
before do
|
||||||
|
@namespace = Oga::XML::Namespace.new(:name => 'x')
|
||||||
|
@element = described_class.new(
|
||||||
|
:namespace_name => 'x',
|
||||||
|
:namespaces => {'x' => @namespace}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'return the namespace' do
|
||||||
|
@element.namespace.should == @namespace
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context '#text' do
|
context '#text' do
|
||||||
|
@ -116,8 +146,9 @@ describe Oga::XML::Element do
|
||||||
|
|
||||||
example 'include the namespace if present' do
|
example 'include the namespace if present' do
|
||||||
instance = described_class.new(
|
instance = described_class.new(
|
||||||
:name => 'p',
|
:name => 'p',
|
||||||
:namespace => Oga::XML::Namespace.new(:name => 'foo')
|
:namespace_name => 'foo',
|
||||||
|
:namespaces => {'foo' => Oga::XML::Namespace.new(:name => 'foo')}
|
||||||
)
|
)
|
||||||
|
|
||||||
instance.to_xml.should == '<foo:p></foo:p>'
|
instance.to_xml.should == '<foo:p></foo:p>'
|
||||||
|
@ -164,11 +195,13 @@ describe Oga::XML::Element do
|
||||||
|
|
||||||
example 'inspect a node with a namespace' do
|
example 'inspect a node with a namespace' do
|
||||||
node = described_class.new(
|
node = described_class.new(
|
||||||
:name => 'p',
|
:name => 'p',
|
||||||
:namespace => Oga::XML::Namespace.new(:name => 'x')
|
:namespace_name => 'x',
|
||||||
|
:namespaces => {'x' => Oga::XML::Namespace.new(:name => 'x')}
|
||||||
)
|
)
|
||||||
|
|
||||||
node.inspect.should == 'Element(name: "p" namespace: Namespace(name: "x"))'
|
node.inspect.should == 'Element(name: "p" ' \
|
||||||
|
'namespace: Namespace(name: "x" uri: nil))'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -178,7 +211,53 @@ describe Oga::XML::Element do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context '#register_namespace' do
|
||||||
|
before do
|
||||||
|
@element = described_class.new
|
||||||
|
|
||||||
|
@element.register_namespace('foo', 'http://example.com')
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'return a Namespace instance' do
|
||||||
|
@element.namespaces['foo'].is_a?(Oga::XML::Namespace).should == true
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'set the name of the namespace' do
|
||||||
|
@element.namespaces['foo'].name.should == 'foo'
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'set the URI of the namespace' do
|
||||||
|
@element.namespaces['foo'].uri.should == 'http://example.com'
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'raise ArgumentError if the namespace already exists' do
|
||||||
|
block = lambda { @element.register_namespace('foo', 'bar') }
|
||||||
|
|
||||||
|
block.should raise_error(ArgumentError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context '#available_namespaces' do
|
context '#available_namespaces' do
|
||||||
# TODO: write me
|
before do
|
||||||
|
@parent = described_class.new
|
||||||
|
@child = described_class.new
|
||||||
|
|
||||||
|
@child.node_set = Oga::XML::NodeSet.new([@child], @parent)
|
||||||
|
|
||||||
|
@parent.register_namespace('foo', 'bar')
|
||||||
|
@child.register_namespace('baz', 'xxx')
|
||||||
|
|
||||||
|
@parent_ns = @parent.available_namespaces
|
||||||
|
@child_ns = @child.available_namespaces
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'return the available namespaces of the child node' do
|
||||||
|
@child_ns['foo'].is_a?(Oga::XML::Namespace).should == true
|
||||||
|
@child_ns['baz'].is_a?(Oga::XML::Namespace).should == true
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'return the available namespaces of the parent node' do
|
||||||
|
@parent_ns['foo'].is_a?(Oga::XML::Namespace).should == true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,9 +15,9 @@ describe Oga::XML::Namespace do
|
||||||
|
|
||||||
context '#inspect' do
|
context '#inspect' do
|
||||||
example 'return the inspect value' do
|
example 'return the inspect value' do
|
||||||
ns = described_class.new(:name => 'x')
|
ns = described_class.new(:name => 'x', :uri => 'y')
|
||||||
|
|
||||||
ns.inspect.should == 'Namespace(name: "x")'
|
ns.inspect.should == 'Namespace(name: "x" uri: "y")'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ describe Oga::XML::Parser do
|
||||||
|
|
||||||
context 'elements with namespaces' do
|
context 'elements with namespaces' do
|
||||||
before :all do
|
before :all do
|
||||||
@element = parse('<foo:p></foo:p>').children[0]
|
@element = parse('<foo:p xmlns:foo="bar"></foo:p>').children[0]
|
||||||
end
|
end
|
||||||
|
|
||||||
example 'return an Element instance' do
|
example 'return an Element instance' do
|
||||||
|
@ -57,7 +57,7 @@ describe Oga::XML::Parser do
|
||||||
|
|
||||||
context 'elements with namespaced attributes' do
|
context 'elements with namespaced attributes' do
|
||||||
before :all do
|
before :all do
|
||||||
@element = parse('<foo x:bar="baz"></foo>').children[0]
|
@element = parse('<foo xmlns:x="x" x:bar="baz"></foo>').children[0]
|
||||||
end
|
end
|
||||||
|
|
||||||
example 'return an Element instance' do
|
example 'return an Element instance' do
|
||||||
|
@ -108,4 +108,32 @@ describe Oga::XML::Parser do
|
||||||
@element.children[1].children[0].is_a?(Oga::XML::Text).should == true
|
@element.children[1].children[0].is_a?(Oga::XML::Text).should == true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'elements with namespace registrations' do
|
||||||
|
before :all do
|
||||||
|
document = parse('<root xmlns:a="1"><foo xmlns:b="2"></foo></root>')
|
||||||
|
|
||||||
|
@root = document.children[0]
|
||||||
|
@foo = @root.children[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'return the namespaces of the <root> node' do
|
||||||
|
@root.namespaces['a'].name.should == 'a'
|
||||||
|
@root.namespaces['a'].uri.should == '1'
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'return the namespaces of the <foo> node' do
|
||||||
|
@foo.namespaces['b'].name.should == 'b'
|
||||||
|
@foo.namespaces['b'].uri.should == '2'
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'return the available namespaces of the <root> node' do
|
||||||
|
@root.available_namespaces['a'].name.should == 'a'
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'return the available namespaces of the <foo> node' do
|
||||||
|
@foo.available_namespaces['a'].name.should == 'a'
|
||||||
|
@foo.available_namespaces['b'].name.should == 'b'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue