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.
|
||||
# @return [String]
|
||||
#
|
||||
# @!attribute [rw] namespace
|
||||
# The namespace of the element, if any.
|
||||
# @return [Oga::XML::Namespace]
|
||||
# @!attribute [ww] namespace_name
|
||||
# The name of the namespace.
|
||||
# @return [String]
|
||||
#
|
||||
# @!attribute [rw] attributes
|
||||
# The attributes of the element.
|
||||
# @return [Array<Oga::XML::Attribute>]
|
||||
#
|
||||
# @!attribute [rw] namespaces
|
||||
# The registered namespaces.
|
||||
# @return [Hash]
|
||||
#
|
||||
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
|
||||
# types of their values.
|
||||
# The attribute prefix/namespace used for registering element namespaces.
|
||||
#
|
||||
# @return [Hash]
|
||||
# @return [String]
|
||||
#
|
||||
OPTION_TYPES = {
|
||||
:namespace => Namespace,
|
||||
:attributes => Array
|
||||
}
|
||||
XMLNS_PREFIX = 'xmlns'.freeze
|
||||
|
||||
##
|
||||
# @param [Hash] options
|
||||
#
|
||||
# @option options [String] :name The name of the element.
|
||||
#
|
||||
# @option options [Oga::XML::Namespace] :namespace The namespace of the
|
||||
# element.
|
||||
# @option options [String] :namespace_name The name of the namespace.
|
||||
#
|
||||
# @option options [Array<Oga::XML::Attribute>] :attributes The attributes
|
||||
# of the element as an Array.
|
||||
#
|
||||
def initialize(options = {})
|
||||
validate_option_types!(options)
|
||||
|
||||
super
|
||||
|
||||
@name = options[:name]
|
||||
@namespace = options[:namespace]
|
||||
@attributes = options[:attributes] || []
|
||||
@name = options[:name]
|
||||
@namespace_name = options[:namespace_name]
|
||||
@attributes = options[:attributes] || []
|
||||
@namespaces = options[:namespaces] || {}
|
||||
|
||||
link_attributes
|
||||
register_namespaces_from_attributes
|
||||
end
|
||||
|
||||
##
|
||||
|
@ -76,6 +77,15 @@ module Oga
|
|||
|
||||
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.
|
||||
#
|
||||
|
@ -146,40 +156,63 @@ module Oga
|
|||
end
|
||||
|
||||
##
|
||||
# Returns a node set of all the namespaces that are available to the
|
||||
# current node. This includes the namespaces registered on the current
|
||||
# node.
|
||||
# Registers a new namespace for the current element and its child
|
||||
# elements.
|
||||
#
|
||||
# @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
|
||||
|
||||
##
|
||||
# Returns a node set of all the namespaces registered with the current
|
||||
# node.
|
||||
# Returns a Hash containing all the namespaces available to the current
|
||||
# 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
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# @param [Hash] options
|
||||
# @raise [TypeError]
|
||||
# Registers namespaces based on any "xmlns" attributes. Once a namespace
|
||||
# has been registered the corresponding attribute is removed.
|
||||
#
|
||||
def validate_option_types!(options)
|
||||
OPTION_TYPES.each do |key, type|
|
||||
if options[key] and !options[key].is_a?(type)
|
||||
raise(
|
||||
TypeError,
|
||||
"#{key.inspect} must be an instance of #{type}"
|
||||
)
|
||||
end
|
||||
def register_namespaces_from_attributes
|
||||
self.attributes = attributes.reject do |attr|
|
||||
# We're using `namespace_name` opposed to `namespace.name` as "xmlns"
|
||||
# is not a registered namespace.
|
||||
remove = attr.namespace_name && attr.namespace_name == XMLNS_PREFIX
|
||||
|
||||
register_namespace(attr.name, attr.value) if remove
|
||||
|
||||
remove
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Links all attributes to the current element.
|
||||
#
|
||||
def link_attributes
|
||||
attributes.each do |attr|
|
||||
attr.element = self
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -206,7 +239,7 @@ module Oga
|
|||
if ns
|
||||
ns_matches = attr.namespace.to_s == ns
|
||||
|
||||
elsif name_matches
|
||||
elsif name_matches and !attr.namespace
|
||||
ns_matches = true
|
||||
end
|
||||
|
||||
|
|
|
@ -336,14 +336,10 @@ Unexpected #{name} with value #{value.inspect} on line #{@line}:
|
|||
# @return [Oga::XML::Element]
|
||||
#
|
||||
def on_element(namespace, name, attributes = {})
|
||||
if namespace
|
||||
namespace = Namespace.new(:name => namespace)
|
||||
end
|
||||
|
||||
element = Element.new(
|
||||
:namespace => namespace,
|
||||
:name => name,
|
||||
:attributes => attributes
|
||||
:namespace_name => namespace,
|
||||
:name => name,
|
||||
:attributes => attributes
|
||||
)
|
||||
|
||||
return element
|
||||
|
|
|
@ -6,18 +6,6 @@ describe Oga::XML::Element do
|
|||
described_class.new(:name => 'p').name.should == 'p'
|
||||
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
|
||||
instance = described_class.new
|
||||
instance.name = 'p'
|
||||
|
@ -30,18 +18,42 @@ describe Oga::XML::Element do
|
|||
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
|
||||
before do
|
||||
attributes = [
|
||||
Oga::XML::Attribute.new(:name => 'key', :value => 'value'),
|
||||
Oga::XML::Attribute.new(
|
||||
:name => 'key',
|
||||
:value => 'foo',
|
||||
:namespace => Oga::XML::Namespace.new(:name => 'x')
|
||||
:name => 'bar',
|
||||
:value => 'baz',
|
||||
: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
|
||||
|
||||
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
|
||||
@instance.attribute('foobar').nil?.should == true
|
||||
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
|
||||
|
||||
context '#text' do
|
||||
|
@ -116,8 +146,9 @@ describe Oga::XML::Element do
|
|||
|
||||
example 'include the namespace if present' do
|
||||
instance = described_class.new(
|
||||
:name => 'p',
|
||||
:namespace => Oga::XML::Namespace.new(:name => 'foo')
|
||||
:name => 'p',
|
||||
:namespace_name => 'foo',
|
||||
:namespaces => {'foo' => Oga::XML::Namespace.new(:name => 'foo')}
|
||||
)
|
||||
|
||||
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
|
||||
node = described_class.new(
|
||||
:name => 'p',
|
||||
:namespace => Oga::XML::Namespace.new(:name => 'x')
|
||||
:name => 'p',
|
||||
: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
|
||||
|
||||
|
@ -178,7 +211,53 @@ describe Oga::XML::Element do
|
|||
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
|
||||
# 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
|
||||
|
|
|
@ -15,9 +15,9 @@ describe Oga::XML::Namespace do
|
|||
|
||||
context '#inspect' 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
|
||||
|
|
|
@ -21,7 +21,7 @@ describe Oga::XML::Parser do
|
|||
|
||||
context 'elements with namespaces' do
|
||||
before :all do
|
||||
@element = parse('<foo:p></foo:p>').children[0]
|
||||
@element = parse('<foo:p xmlns:foo="bar"></foo:p>').children[0]
|
||||
end
|
||||
|
||||
example 'return an Element instance' do
|
||||
|
@ -57,7 +57,7 @@ describe Oga::XML::Parser do
|
|||
|
||||
context 'elements with namespaced attributes' 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
|
||||
|
||||
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
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue