1 require 'rexml/functions'
2 require 'rexml/xmltokens'
11 def QuickPath::first element, path, namespaces=EMPTY_HASH
12 match(element, path, namespaces)[0]
15 def QuickPath::each element, path, namespaces=EMPTY_HASH, &block
16 path = "*" unless path
17 match(element, path, namespaces).each( &block )
20 def QuickPath::match element, path, namespaces=EMPTY_HASH
21 raise "nil is not a valid xpath" unless path
23 Functions::namespace_context = namespaces
28 return [element.root.parent] if path == ''
29 results = filter([element.root], path)
31 results = filter([element], path)
33 results = filter(element.to_a, path)
37 children = element.to_a
38 results = filter(children, path)
40 results = filter([element], path)
45 # Given an array of nodes it filters the array based on the path. The
46 # result is that when this method returns, the array will contain elements
47 # which match the path
48 def QuickPath::filter elements, path
49 return elements if path.nil? or path == '' or elements.size == 0
51 when /^\/\//u # Descendant
52 return axe( elements, "descendant-or-self", $' )
53 when /^\/?\b(\w[-\w]*)\b::/u # Axe
56 return axe( elements, $1, $' )
57 when /^\/(?=\b([:!\w][-\.\w]*:)?[-!\*\.\w]*\b([^:(]|$)|\*)/u # Child
60 elements.each do |element|
61 results |= filter( element.to_a, rest )
64 when /^\/?(\w[-\w]*)\(/u # / Function
65 return function( elements, $1, $' )
66 when Namespace::NAMESPLIT # Element name
70 elements.delete_if do |element|
71 !(element.kind_of? Element and
72 (element.expanded_name == name or
73 (element.name == name and
74 element.namespace == Functions.namespace_context[ns])))
76 return filter( elements, rest )
79 elements.each do |element|
80 matches |= predicate( element.to_a, path[1..-1] ) if element.kind_of? Element
83 when /^\[/u # Predicate
84 return predicate( elements, path )
85 when /^\/?\.\.\./u # Ancestor
86 return axe( elements, "ancestor", $' )
87 when /^\/?\.\./u # Parent
88 return filter( elements.collect{|e|e.parent}, $' )
90 return filter( elements, $' )
93 elements.each do |element|
94 results |= filter( [element], $' ) if element.kind_of? Element
95 #if element.kind_of? Element
96 # children = element.to_a
97 # children.delete_if { |child| !child.kind_of?(Element) }
98 # results |= filter( children, $' )
106 def QuickPath::axe( elements, axe_name, rest )
108 matches = filter( elements.dup, rest ) if axe_name =~ /-or-self$/u
111 elements.each do |element|
112 matches |= filter( element.to_a, "descendant-or-self::#{rest}" ) if element.kind_of? Element
115 elements.each do |element|
117 matches << element.parent
118 element = element.parent
121 matches = filter( matches, rest )
123 matches = filter( elements, rest )
125 elements.each do |element|
126 matches |= filter( element.to_a, rest ) if element.kind_of? Element
129 elements.each do |element|
130 matches << element.attributes[ rest ] if element.kind_of? Element
133 matches = filter(elements.collect{|element| element.parent}.uniq, rest)
134 when "following-sibling"
135 matches = filter(elements.collect{|element| element.next_sibling}.uniq,
137 when "previous-sibling"
138 matches = filter(elements.collect{|element|
139 element.previous_sibling}.uniq, rest )
144 # A predicate filters a node-set with respect to an axis to produce a
145 # new node-set. For each node in the node-set to be filtered, the
146 # PredicateExpr is evaluated with that node as the context node, with
147 # the number of nodes in the node-set as the context size, and with the
148 # proximity position of the node in the node-set with respect to the
149 # axis as the context position; if PredicateExpr evaluates to true for
150 # that node, the node is included in the new node-set; otherwise, it is
153 # A PredicateExpr is evaluated by evaluating the Expr and converting
154 # the result to a boolean. If the result is a number, the result will
155 # be converted to true if the number is equal to the context position
156 # and will be converted to false otherwise; if the result is not a
157 # number, then the result will be converted as if by a call to the
158 # boolean function. Thus a location path para[3] is equivalent to
159 # para[position()=3].
160 def QuickPath::predicate( elements, path )
164 bcount += 1 if path[ind] == ?[
165 bcount -= 1 if path[ind] == ?]
169 predicate = path[1..ind-1]
170 rest = path[ind+1..-1]
172 # have to change 'a [=<>] b [=<>] c' into 'a [=<>] b and b [=<>] c'
173 predicate.gsub!( /([^\s(and)(or)<>=]+)\s*([<>=])\s*([^\s(and)(or)<>=]+)\s*([<>=])\s*([^\s(and)(or)<>=]+)/u ) {
174 "#$1 #$2 #$3 and #$3 #$4 #$5"
176 # Let's do some Ruby trickery to avoid some work:
177 predicate.gsub!( /&/u, "&&" )
178 predicate.gsub!( /=/u, "==" )
179 predicate.gsub!( /@(\w[-\w.]*)/u ) {
182 predicate.gsub!( /\bmod\b/u, "%" )
183 predicate.gsub!( /\b(\w[-\w.]*\()/u ) {
185 fname.gsub( /-/u, "_" )
188 Functions.pair = [ 0, elements.size ]
190 elements.each do |element|
191 Functions.pair[0] += 1
192 Functions.node = element
193 res = eval( predicate )
198 results << element if Functions.pair[0] == res
203 return filter( results, rest )
206 def QuickPath::attribute( name )
207 return Functions.node.attributes[name] if Functions.node.kind_of? Element
210 def QuickPath::name()
211 return Functions.node.name if Functions.node.kind_of? Element
214 def QuickPath::method_missing( id, *args )
216 Functions.send( id.id2name, *args )
218 raise "METHOD: #{id.id2name}(#{args.join ', '})\n#{$!.message}"
222 def QuickPath::function( elements, fname, rest )
223 args = parse_args( elements, rest )
224 Functions.pair = [0, elements.size]
226 elements.each do |element|
227 Functions.pair[0] += 1
228 Functions.node = element
229 res = Functions.send( fname, *args )
234 results << element if Functions.pair[0] == res
240 def QuickPath::parse_args( element, string )
244 while string and string != ""
246 string.sub!(/^./u, "")
249 # if depth = 1, then we start a new argument
250 arguments << evaluate( buffer )
251 #arguments << evaluate( string[0..count] )
253 # start a new method call
254 function( element, buffer, string )
257 # close the method call and return arguments