Skip to content

Commit 8f43af5

Browse files
naitohtompng
andcommitted
Optimization of Element#namespace and Element#namespaces with cache
## Why? for fix get_namespace performance > # FIXME: This DOUBLES the time XPath searches take Co-authored-by: tomoya ishida <tomoyapenguin@gmail.com>
1 parent 416c487 commit 8f43af5

2 files changed

Lines changed: 32 additions & 29 deletions

File tree

lib/rexml/element.rb

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -588,11 +588,12 @@ def prefixes
588588
# d.elements['//b'].namespaces # => {"x"=>"1", "y"=>"2"}
589589
# d.elements['//c'].namespaces # => {"x"=>"1", "y"=>"2", "z"=>"3"}
590590
#
591-
def namespaces
592-
namespaces = {}
593-
namespaces = parent.namespaces if parent
594-
namespaces = namespaces.merge( attributes.namespaces )
595-
return namespaces
591+
def namespaces(cache = nil)
592+
if cache
593+
cache[self] ||= calculate_namespaces(cache)
594+
else
595+
calculate_namespaces(cache)
596+
end
596597
end
597598

598599
# :call-seq:
@@ -615,21 +616,13 @@ def namespaces
615616
# b.namespace('y') # => "2"
616617
# b.namespace('nosuch') # => nil
617618
#
618-
def namespace(prefix=nil)
619+
def namespace(prefix=nil, cache=nil)
619620
if prefix.nil?
620621
prefix = prefix()
621622
end
622-
if prefix == ''
623-
prefix = "xmlns"
624-
else
625-
prefix = "xmlns:#{prefix}" unless prefix[0,5] == 'xmlns'
626-
end
627-
ns = nil
628-
target = self
629-
while ns.nil? and target
630-
ns = target.attributes[prefix]
631-
target = target.parent
632-
end
623+
prefix = (prefix == '') ? 'xmlns' : prefix.delete_prefix("xmlns:")
624+
ns = namespaces(cache)[prefix]
625+
633626
ns = '' if ns.nil? and prefix == 'xmlns'
634627
return ns
635628
end
@@ -1516,8 +1509,11 @@ def write(output=$stdout, indent=-1, transitive=false, ie_hack=false)
15161509
formatter.write( self, output )
15171510
end
15181511

1519-
15201512
private
1513+
def calculate_namespaces(cache)
1514+
(parent ? parent.namespaces(cache) : {}).merge(attributes.namespaces)
1515+
end
1516+
15211517
def __to_xpath_helper node
15221518
rv = node.expanded_name.clone
15231519
if node.parent

lib/rexml/xpath_parser.rb

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,11 @@ def strict?
160160
#
161161
# 1. Use the supplied namespace mapping first.
162162
# 2. If no mapping was supplied, use the context node to look up the namespace
163-
def get_namespace( node, prefix )
163+
def get_namespace( node, prefix, namespaces_cache=nil )
164164
if @namespaces
165165
return @namespaces[prefix] || ''
166166
else
167-
return node.namespace( prefix ) if node.node_type == :element
167+
return node.namespace( prefix, namespaces_cache ) if node.node_type == :element
168168
return ''
169169
end
170170
end
@@ -476,6 +476,7 @@ def step(path_stack, any_type: :element, order: :forward)
476476

477477
def node_test(path_stack, nodesets, any_type: :element)
478478
enter(:node_test, path_stack, nodesets) if @debug
479+
namespaces_cache = {}
479480
operator = path_stack.shift
480481
case operator
481482
when :qname
@@ -492,24 +493,30 @@ def node_test(path_stack, nodesets, any_type: :element)
492493
if strict?
493494
raw_node.name == name and raw_node.namespace == ""
494495
else
495-
# FIXME: This DOUBLES the time XPath searches take
496-
ns = get_namespace(raw_node, prefix)
497-
raw_node.name == name and raw_node.namespace == ns
496+
if raw_node.name == name
497+
raw_node.namespace(nil, namespaces_cache) == get_namespace(raw_node, prefix, namespaces_cache)
498+
else
499+
false
500+
end
498501
end
499502
else
500-
# FIXME: This DOUBLES the time XPath searches take
501-
ns = get_namespace(raw_node, prefix)
502-
raw_node.name == name and raw_node.namespace == ns
503+
if raw_node.name == name
504+
raw_node.namespace(nil, namespaces_cache) == get_namespace(raw_node, prefix, namespaces_cache)
505+
else
506+
false
507+
end
503508
end
504509
when :attribute
505510
if prefix.nil?
506511
raw_node.name == name
507512
elsif prefix.empty?
508513
raw_node.name == name and raw_node.namespace == ""
509514
else
510-
# FIXME: This DOUBLES the time XPath searches take
511-
ns = get_namespace(raw_node.element, prefix)
512-
raw_node.name == name and raw_node.namespace == ns
515+
if raw_node.name == name
516+
raw_node.namespace == get_namespace(raw_node.element, prefix)
517+
else
518+
false
519+
end
513520
end
514521
else
515522
false

0 commit comments

Comments
 (0)