Skip to content

Commit 7a16a63

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 7a16a63

4 files changed

Lines changed: 41 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: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,13 @@ def variables=( vars={} )
7878

7979
def parse path, nodeset
8080
path_stack = @parser.parse( path )
81-
match( path_stack, nodeset )
81+
if nodeset.empty?
82+
match( path_stack, nodeset )
83+
else
84+
nodeset.first.document.send(:enable_cache) do
85+
match( path_stack, nodeset )
86+
end
87+
end
8288
end
8389

8490
def get_first path, nodeset
@@ -492,24 +498,18 @@ def node_test(path_stack, nodesets, any_type: :element)
492498
if strict?
493499
raw_node.name == name and raw_node.namespace == ""
494500
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
501+
raw_node.name == name and raw_node.namespace == get_namespace(raw_node, prefix)
498502
end
499503
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
504+
raw_node.name == name and raw_node.namespace == get_namespace(raw_node, prefix)
503505
end
504506
when :attribute
505507
if prefix.nil?
506508
raw_node.name == name
507509
elsif prefix.empty?
508510
raw_node.name == name and raw_node.namespace == ""
509511
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
512+
raw_node.name == name and raw_node.namespace == get_namespace(raw_node.element, prefix)
513513
end
514514
else
515515
false

0 commit comments

Comments
 (0)