Cache output of Element#available_namespaces

This cache is flushed whenever Element#register_namespace is called.
When this cache is flushed it's also recursively flushed for all child
elements. This makes calls to Element#register_namespace a bit more
expensive but in turn calls to Element#available_namespaces will be a
lot faster.
This commit is contained in:
Yorick Peterse 2015-04-12 20:22:33 +02:00
parent fa838154fc
commit b42f9aaf32
2 changed files with 83 additions and 10 deletions

View File

@ -299,14 +299,17 @@ module Oga
#
# @param [String] name
# @param [String] uri
# @param [TrueClass|FalseClass] flush
# @see [Oga::XML::Namespace#initialize]
#
def register_namespace(name, uri)
def register_namespace(name, uri, flush = true)
if namespaces[name]
raise ArgumentError, "The namespace #{name.inspect} already exists"
end
namespaces[name] = Namespace.new(:name => name, :uri => uri)
flush_namespaces_cache if flush
end
##
@ -316,20 +319,25 @@ module Oga
# @return [Hash]
#
def available_namespaces
return {} if html? # HTML(5) completely ignores namespaces
# HTML(5) completely ignores namespaces
if html?
return @available_namespaces ||= {}
elsif !@available_namespaces
merged = namespaces.dup
node = parent
merged = namespaces.dup
node = parent
while node && node.respond_to?(:namespaces)
node.namespaces.each do |prefix, ns|
merged[prefix] = ns unless merged[prefix]
end
while node && node.respond_to?(:namespaces)
node.namespaces.each do |prefix, ns|
merged[prefix] = ns unless merged[prefix]
node = node.parent
end
node = node.parent
@available_namespaces = merged
end
return merged
return @available_namespaces
end
##
@ -349,19 +357,40 @@ module Oga
return self_closing
end
##
# Flushes the namespaces cache of the current element and all its child
# elements.
#
def flush_namespaces_cache
@available_namespaces = nil
@namespace = nil
children.each do |child|
child.flush_namespaces_cache if child.is_a?(Element)
end
end
private
##
# Registers namespaces based on any "xmlns" attributes.
#
def register_namespaces_from_attributes
flush = false
attributes.each do |attr|
# We're using `namespace_name` opposed to `namespace.name` as "xmlns"
# is not a registered namespace.
if attr.name == XMLNS_PREFIX or attr.namespace_name == XMLNS_PREFIX
register_namespace(attr.name, attr.value)
flush = true
# Ensures we only flush the cache once instead of flushing it on
# every register_namespace call.
register_namespace(attr.name, attr.value, false)
end
end
flush_namespaces_cache if flush
end
##

View File

@ -488,6 +488,25 @@ describe Oga::XML::Element do
block.should raise_error(ArgumentError)
end
it 'flushes the cache when registering a namespace' do
@element.available_namespaces.should == {
'foo' => @element.namespaces['foo']
}
@element.register_namespace('bar', 'http://exmaple.com')
@element.available_namespaces.should == {
'foo' => @element.namespaces['foo'],
'bar' => @element.namespaces['bar']
}
end
it 'does not flush the cache when "flush" is set to false' do
@element.should_not receive(:flush_namespaces_cache)
@element.register_namespace('bar', 'http://example.com', false)
end
end
describe '#available_namespaces' do
@ -564,4 +583,29 @@ describe Oga::XML::Element do
element.should_not be_self_closing
end
end
describe '#flush_namespaces_cache' do
it 'flushes the namespaces cache of the current element' do
element = described_class.new(:name => 'a')
element.available_namespaces.should == {}
element.register_namespace('foo', 'bar', false)
element.flush_namespaces_cache
element.available_namespaces.should == {
'foo' => element.namespaces['foo']
}
end
it 'flushes the namespace cache of all child elements' do
child = described_class.new(:name => 'b')
parent = described_class.new(:name => 'a', :children => [child])
child.should_receive(:flush_namespaces_cache)
parent.flush_namespaces_cache
end
end
end