Skip to content

Commit 6ae3d53

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 68b0a40 commit 6ae3d53

4 files changed

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

7979
def parse path, node
8080
path_stack = @parser.parse( path )
81-
match( path_stack, node )
81+
if node.is_a?(Array)
82+
if node.empty?
83+
match( path_stack, node )
84+
else
85+
node.first.document.send(:enable_cache) do
86+
match( path_stack, node )
87+
end
88+
end
89+
else
90+
node.document.send(:enable_cache) do
91+
match( path_stack, node )
92+
end
93+
end
8294
end
8395

8496
def get_first path, node
@@ -494,24 +506,18 @@ def node_test(path_stack, nodesets, any_type: :element)
494506
if strict?
495507
raw_node.name == name and raw_node.namespace == ""
496508
else
497-
# FIXME: This DOUBLES the time XPath searches take
498-
ns = get_namespace(raw_node, prefix)
499-
raw_node.name == name and raw_node.namespace == ns
509+
raw_node.name == name and raw_node.namespace == get_namespace(raw_node, prefix)
500510
end
501511
else
502-
# FIXME: This DOUBLES the time XPath searches take
503-
ns = get_namespace(raw_node, prefix)
504-
raw_node.name == name and raw_node.namespace == ns
512+
raw_node.name == name and raw_node.namespace == get_namespace(raw_node, prefix)
505513
end
506514
when :attribute
507515
if prefix.nil?
508516
raw_node.name == name
509517
elsif prefix.empty?
510518
raw_node.name == name and raw_node.namespace == ""
511519
else
512-
# FIXME: This DOUBLES the time XPath searches take
513-
ns = get_namespace(raw_node.element, prefix)
514-
raw_node.name == name and raw_node.namespace == ns
520+
raw_node.name == name and raw_node.namespace == get_namespace(raw_node.element, prefix)
515521
end
516522
else
517523
false

0 commit comments

Comments
 (0)