@@ -63,6 +63,9 @@ def initialize(strict: false)
6363 @namespaces = nil
6464 @variables = { }
6565 @functions = FunctionsClass . new
66+ @attrlist_per_element_namespaces = nil
67+ @document = nil
68+ @element_namespaces_cache = { }
6669 @nest = 0
6770 @strict = strict
6871 end
@@ -85,14 +88,8 @@ def parse path, node
8588 node = node . first
8689 end
8790
88- document = node . document
89- if document
90- document . __send__ ( :enable_cache ) do
91- match ( path_stack , node )
92- end
93- else
94- match ( path_stack , node )
95- end
91+ @document = node . document
92+ match ( path_stack , node )
9693 end
9794
9895 def get_first path , node
@@ -150,7 +147,6 @@ def first( path_stack, node )
150147 end
151148 end
152149
153-
154150 def match ( path_stack , node )
155151 nodeset = [ node ]
156152 result = expr ( path_stack , nodeset )
@@ -167,20 +163,45 @@ def strict?
167163 @strict
168164 end
169165
170- # Returns a String namespace for a node, given a prefix
166+ # Returns a String namespace for a prefix used in xpath.
171167 # The rules are:
172168 #
173169 # 1. Use the supplied namespace mapping first.
174- # 2. If no mapping was supplied, use the context node to look up the namespace
175- def get_namespace ( node , prefix )
170+ # 2. If no mapping was supplied, use the context node to look up the namespace as a fallback.
171+ def get_xpath_namespace ( node , prefix )
176172 if @namespaces
177173 @namespaces [ prefix ] || ''
174+ elsif node . node_type == :element
175+ element_namespace_lookup ( node , prefix )
178176 else
179- return node . namespace ( prefix ) if node . node_type == :element
180177 ''
181178 end
182179 end
183180
181+ # Returns attribute's namespace URI while caching the
182+ # intermediate result to speed up retrieval of namespaces
183+ def attribute_namespace ( attribute )
184+ attribute . prefix == '' ? '' : element_namespace_lookup ( attribute . element , attribute . prefix )
185+ end
186+
187+ # Return element's namespace URI while caching the
188+ # intermediate result to speed up retrieval of namespaces
189+ def element_namespace ( element )
190+ element . send ( :namespace_internal , element_namespaces ( element ) )
191+ end
192+
193+ # Returns a hash of namespaces for the given element while caching the
194+ # intermediate result to speed up retrieval of namespaces
195+ def element_namespaces ( element )
196+ @attrlist_per_element_namespaces ||= @document &.send ( :attrlist_per_element_namespaces ) || { }
197+ element . send ( :calculate_namespaces , @element_namespaces_cache , @attrlist_per_element_namespaces )
198+ end
199+
200+ # Returns namespace of the prefix in the context of the element,
201+ # while caching the intermediate result to speed up retrieval of namespaces
202+ def element_namespace_lookup ( element , prefix )
203+ element . send ( :namespace_lookup_internal , prefix , element_namespaces ( element ) )
204+ end
184205
185206 # Expr takes a stack of path elements and a set of nodes (either a Parent
186207 # or an Array and returns an Array of matching nodes
@@ -641,20 +662,20 @@ def node_test(path_stack, any_type: :element)
641662 node . name == name
642663 elsif prefix . empty?
643664 if strict?
644- node . name == name and node . namespace == ""
665+ node . name == name and element_namespace ( node ) == ""
645666 else
646- node . name == name and node . namespace == get_namespace ( node , prefix )
667+ node . name == name and element_namespace ( node ) == get_xpath_namespace ( node , prefix )
647668 end
648669 else
649- node . name == name and node . namespace == get_namespace ( node , prefix )
670+ node . name == name and element_namespace ( node ) == get_xpath_namespace ( node , prefix )
650671 end
651672 when :attribute
652673 if prefix . nil?
653674 node . name == name
654675 elsif prefix . empty?
655- node . name == name and node . namespace == ""
676+ node . name == name and attribute_namespace ( node ) == ""
656677 else
657- node . name == name and node . namespace == get_namespace ( node . element , prefix )
678+ node . name == name and attribute_namespace ( node ) == get_xpath_namespace ( node . element , prefix )
658679 end
659680 else
660681 false
@@ -665,11 +686,11 @@ def node_test(path_stack, any_type: :element)
665686 -> ( node ) do
666687 case node . node_type
667688 when :element
668- namespaces = @namespaces || node . namespaces
669- node . namespace == namespaces [ prefix ]
689+ namespaces = @namespaces || element_namespaces ( node )
690+ element_namespace ( node ) == namespaces [ prefix ]
670691 when :attribute
671- namespaces = @namespaces || node . element . namespaces
672- node . namespace == namespaces [ prefix ]
692+ namespaces = @namespaces || element_namespaces ( node . element )
693+ attribute_namespace ( node ) == namespaces [ prefix ]
673694 else
674695 false
675696 end
0 commit comments