Skip to content

Commit 93a46eb

Browse files
naitohtompngkou
committed
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> Co-authored-by: Sutou Kouhei <kou@clear-code.com>
1 parent 90d3332 commit 93a46eb

4 files changed

Lines changed: 42 additions & 26 deletions

File tree

lib/rexml/attribute.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ def xpath
206206
path += "/@#{self.expanded_name}"
207207
return path
208208
end
209+
210+
def document
211+
self.element.document
212+
end
209213
end
210214
end
211215
#vim:ts=2 sw=2 noexpandtab:

lib/rexml/document.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,20 @@ def document
448448
end
449449

450450
private
451+
452+
attr_accessor :namespaces_cache
453+
454+
# New document level cache is created and available in this block.
455+
# This API is thread unsafe. Users can't change this document in this block.
456+
def enable_cache
457+
self.namespaces_cache = {}
458+
begin
459+
yield
460+
ensure
461+
self.namespaces_cache = nil
462+
end
463+
end
464+
451465
def build( source )
452466
Parsers::TreeParser.new( source, self ).parse
453467
end

lib/rexml/element.rb

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -589,10 +589,12 @@ def prefixes
589589
# d.elements['//c'].namespaces # => {"x"=>"1", "y"=>"2", "z"=>"3"}
590590
#
591591
def namespaces
592-
namespaces = {}
593-
namespaces = parent.namespaces if parent
594-
namespaces = namespaces.merge( attributes.namespaces )
595-
return namespaces
592+
namespaces_cache = document&.send(:namespaces_cache)
593+
if namespaces_cache
594+
namespaces_cache[self] ||= calculate_namespaces
595+
else
596+
calculate_namespaces
597+
end
596598
end
597599

598600
# :call-seq:
@@ -619,17 +621,9 @@ def namespace(prefix=nil)
619621
if prefix.nil?
620622
prefix = prefix()
621623
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
624+
prefix = (prefix == '') ? 'xmlns' : prefix.delete_prefix("xmlns:")
625+
ns = namespaces[prefix]
626+
633627
ns = '' if ns.nil? and prefix == 'xmlns'
634628
return ns
635629
end
@@ -1516,8 +1510,11 @@ def write(output=$stdout, indent=-1, transitive=false, ie_hack=false)
15161510
formatter.write( self, output )
15171511
end
15181512

1519-
15201513
private
1514+
def calculate_namespaces
1515+
(parent ? parent.namespaces : {}).merge(attributes.namespaces)
1516+
end
1517+
15211518
def __to_xpath_helper node
15221519
rv = node.expanded_name.clone
15231520
if node.parent

lib/rexml/xpath_parser.rb

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,14 @@ def variables=( vars={} )
7878

7979
def parse path, nodeset
8080
path_stack = @parser.parse( path )
81-
match( path_stack, nodeset )
81+
result = []
82+
nodeset.each do |node|
83+
res = node.document.send(:enable_cache) do
84+
match( path_stack, [node] )
85+
end
86+
result.concat res
87+
end
88+
result
8289
end
8390

8491
def get_first path, nodeset
@@ -492,24 +499,18 @@ def node_test(path_stack, nodesets, any_type: :element)
492499
if strict?
493500
raw_node.name == name and raw_node.namespace == ""
494501
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
502+
raw_node.name == name and raw_node.namespace == get_namespace(raw_node, prefix)
498503
end
499504
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
505+
raw_node.name == name and raw_node.namespace == get_namespace(raw_node, prefix)
503506
end
504507
when :attribute
505508
if prefix.nil?
506509
raw_node.name == name
507510
elsif prefix.empty?
508511
raw_node.name == name and raw_node.namespace == ""
509512
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
513+
raw_node.name == name and raw_node.namespace == get_namespace(raw_node.element, prefix)
513514
end
514515
else
515516
false

0 commit comments

Comments
 (0)