From 04cbbdcf9e0fc2a19d04f1c55bd26c40d7a448dd Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 11 Aug 2014 00:40:17 +0200 Subject: [PATCH] Proper namespace support for attributes. This separates namespace handling into namespace names and namespace objects. The namespace objects are retrieved from the element an attribute belongs to. Once retrieved the namespace is cached, due to the overhead of retrieving namespaces in large documents. --- lib/oga/xml/attribute.rb | 37 ++++++++++++++++----------- lib/oga/xml/namespace.rb | 12 +++++++-- lib/oga/xml/parser.y | 4 +-- spec/oga/xml/attribute_spec.rb | 46 +++++++++++++++++++++------------- 4 files changed, 63 insertions(+), 36 deletions(-) diff --git a/lib/oga/xml/attribute.rb b/lib/oga/xml/attribute.rb index 7b940bb..1acefb2 100644 --- a/lib/oga/xml/attribute.rb +++ b/lib/oga/xml/attribute.rb @@ -7,35 +7,44 @@ module Oga # The name of the attribute. # @return [String] # - # @!attribute [rw] namespace - # The namespace of the attribute. - # @return [Oga::XML::Namespace] + # @!attribute [rw] namespace_name + # @return [String] # # @!attribute [rw] value # The value of the attribute. # @return [String] # + # @!attribute [r] element + # The element this attribute belongs to. + # @return [Oga::XML::Element] + # class Attribute - attr_accessor :name, :namespace, :value + attr_accessor :name, :namespace_name, :element, :value ## # @param [Hash] options # # @option options [String] :name - # @option options [Oga::XML::Namespace] :namespace + # @option options [String] :namespace_name # @option options [String] :value + # @option options [Oga::XML::Element] :element # def initialize(options = {}) - if options[:namespace] and !options[:namespace].is_a?(Namespace) - raise( - TypeError, - ':namespace must be an instance of Oga::XML::Namespace' - ) - end + @name = options[:name] + @value = options[:value] + @element = options[:element] - @name = options[:name] - @namespace = options[:namespace] - @value = options[:value] + @namespace_name = options[:namespace_name] + end + + ## + # Returns the {Oga::XML::Namespace} instance for the current namespace + # name. + # + # @return [Oga::XML::Namespace] + # + def namespace + return @namespace ||= element.available_namespaces[namespace_name] end ## diff --git a/lib/oga/xml/namespace.rb b/lib/oga/xml/namespace.rb index 499802c..213668d 100644 --- a/lib/oga/xml/namespace.rb +++ b/lib/oga/xml/namespace.rb @@ -4,16 +4,24 @@ module Oga # The Namespace class contains information about XML namespaces such as the # name and URI. # + # @!attribute [r] name + # @return [String] + # + # @!attribute [r] uri + # @return [String] + # class Namespace - attr_accessor :name + attr_accessor :name, :uri ## # @param [Hash] options # # @option options [String] :name + # @option options [String] :uri # def initialize(options = {}) @name = options[:name] + @uri = options[:uri] end ## @@ -27,7 +35,7 @@ module Oga # @return [String] # def inspect - return "Namespace(name: #{name.inspect})" + return "Namespace(name: #{name.inspect} uri: #{uri.inspect})" end end # Namespace end # XML diff --git a/lib/oga/xml/parser.y b/lib/oga/xml/parser.y index 9e12bc1..003f667 100644 --- a/lib/oga/xml/parser.y +++ b/lib/oga/xml/parser.y @@ -150,9 +150,7 @@ rule # foo:bar | T_ATTR_NS T_ATTR { - ns = Namespace.new(:name => val[0]) - - Attribute.new(:namespace => ns, :name => val[1]) + Attribute.new(:namespace_name => val[0], :name => val[1]) } ; diff --git a/spec/oga/xml/attribute_spec.rb b/spec/oga/xml/attribute_spec.rb index 8b940fb..be08cf7 100644 --- a/spec/oga/xml/attribute_spec.rb +++ b/spec/oga/xml/attribute_spec.rb @@ -6,24 +6,31 @@ describe Oga::XML::Attribute do described_class.new(:name => 'a').name.should == 'a' end - example 'set the namespace' do - ns = Oga::XML::Namespace.new(:name => 'foo') - attr = described_class.new(:namespace => ns) - - attr.namespace.should == ns - end - - example 'raise TypeError when using a String for the namespace' do - block = lambda { described_class.new(:namespace => 'x') } - - block.should raise_error(TypeError) - end - example 'set the value' do described_class.new(:value => 'a').value.should == 'a' end end + context '#namespace' do + before do + @namespace = Oga::XML::Namespace.new(:name => 'b') + + element = Oga::XML::Element.new( + :namespaces => {'b' => @namespace} + ) + + @attribute = described_class.new( + :namespace_name => 'b', + :name => 'a', + :element => element + ) + end + + example 'return a Namespace instance' do + @attribute.namespace.should == @namespace + end + end + context '#to_s' do example 'return an empty String when there is no value' do described_class.new.to_s.should == '' @@ -44,14 +51,19 @@ describe Oga::XML::Attribute do context '#inspect' do example 'return the inspect value' do + element = Oga::XML::Element.new( + :namespaces => {'b' => Oga::XML::Namespace.new(:name => 'b')} + ) + obj = described_class.new( - :name => 'a', - :namespace => Oga::XML::Namespace.new(:name => 'b'), - :value => 'c' + :namespace_name => 'b', + :name => 'a', + :value => 'c', + :element => element ) obj.inspect.should == <<-EOF.strip -Attribute(name: "a" namespace: Namespace(name: "b") value: "c") +Attribute(name: "a" namespace: Namespace(name: "b" uri: nil) value: "c") EOF end end