Skip to content

Commit 003bfc9

Browse files
naitohtompngkou
committed
Optimization of Element#namespace and Element#namespaces with Document#enable_namespaces_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 416c487 commit 003bfc9

3 files changed

Lines changed: 36 additions & 26 deletions

File tree

lib/rexml/document.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,17 @@ def record_entity_expansion
443443
end
444444
end
445445

446+
attr_accessor :namespaces_cache
447+
448+
def enable_namespaces_cache
449+
self.namespaces_cache = {}
450+
begin
451+
yield
452+
ensure
453+
self.namespaces_cache = nil
454+
end
455+
end
456+
446457
def document
447458
self
448459
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 = root.document&.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: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,15 @@ def variables=( vars={} )
7878

7979
def parse path, nodeset
8080
path_stack = @parser.parse( path )
81-
match( path_stack, nodeset )
81+
if nodeset.first.node_type == :attribute
82+
match( path_stack, nodeset )
83+
else
84+
nodeset.first.document.enable_namespaces_cache do
85+
# New document level cache is created and available in this block.
86+
# This API is thread unsafe. Users can't change this document in this block.
87+
match( path_stack, nodeset )
88+
end
89+
end
8290
end
8391

8492
def get_first path, nodeset
@@ -492,24 +500,18 @@ def node_test(path_stack, nodesets, any_type: :element)
492500
if strict?
493501
raw_node.name == name and raw_node.namespace == ""
494502
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
503+
raw_node.name == name and raw_node.namespace == get_namespace(raw_node, prefix)
498504
end
499505
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
506+
raw_node.name == name and raw_node.namespace == get_namespace(raw_node, prefix)
503507
end
504508
when :attribute
505509
if prefix.nil?
506510
raw_node.name == name
507511
elsif prefix.empty?
508512
raw_node.name == name and raw_node.namespace == ""
509513
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
514+
raw_node.name == name and raw_node.namespace == get_namespace(raw_node.element, prefix)
513515
end
514516
else
515517
false

0 commit comments

Comments
 (0)