source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/rexml/quickpath.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 7.7 KB
Line 
1require 'rexml/functions'
2require 'rexml/xmltokens'
3
4module REXML
5 class QuickPath
6 include Functions
7 include XMLTokens
8
9 EMPTY_HASH = {}
10
11 def QuickPath::first element, path, namespaces=EMPTY_HASH
12 match(element, path, namespaces)[0]
13 end
14
15 def QuickPath::each element, path, namespaces=EMPTY_HASH, &block
16 path = "*" unless path
17 match(element, path, namespaces).each( &block )
18 end
19
20 def QuickPath::match element, path, namespaces=EMPTY_HASH
21 raise "nil is not a valid xpath" unless path
22 results = nil
23 Functions::namespace_context = namespaces
24 case path
25 when /^\/([^\/]|$)/u
26 # match on root
27 path = path[1..-1]
28 return [element.root.parent] if path == ''
29 results = filter([element.root], path)
30 when /^[-\w]*::/u
31 results = filter([element], path)
32 when /^\*/u
33 results = filter(element.to_a, path)
34 when /^[\[!\w:]/u
35 # match on child
36 matches = []
37 children = element.to_a
38 results = filter(children, path)
39 else
40 results = filter([element], path)
41 end
42 return results
43 end
44
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
50 case path
51 when /^\/\//u # Descendant
52 return axe( elements, "descendant-or-self", $' )
53 when /^\/?\b(\w[-\w]*)\b::/u # Axe
54 axe_name = $1
55 rest = $'
56 return axe( elements, $1, $' )
57 when /^\/(?=\b([:!\w][-\.\w]*:)?[-!\*\.\w]*\b([^:(]|$)|\*)/u # Child
58 rest = $'
59 results = []
60 elements.each do |element|
61 results |= filter( element.to_a, rest )
62 end
63 return results
64 when /^\/?(\w[-\w]*)\(/u # / Function
65 return function( elements, $1, $' )
66 when Namespace::NAMESPLIT # Element name
67 name = $2
68 ns = $1
69 rest = $'
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])))
75 end
76 return filter( elements, rest )
77 when /^\/\[/u
78 matches = []
79 elements.each do |element|
80 matches |= predicate( element.to_a, path[1..-1] ) if element.kind_of? Element
81 end
82 return matches
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}, $' )
89 when /^\/?\./u # Self
90 return filter( elements, $' )
91 when /^\*/u # Any
92 results = []
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, $' )
99 #end
100 end
101 return results
102 end
103 return []
104 end
105
106 def QuickPath::axe( elements, axe_name, rest )
107 matches = []
108 matches = filter( elements.dup, rest ) if axe_name =~ /-or-self$/u
109 case axe_name
110 when /^descendant/u
111 elements.each do |element|
112 matches |= filter( element.to_a, "descendant-or-self::#{rest}" ) if element.kind_of? Element
113 end
114 when /^ancestor/u
115 elements.each do |element|
116 while element.parent
117 matches << element.parent
118 element = element.parent
119 end
120 end
121 matches = filter( matches, rest )
122 when "self"
123 matches = filter( elements, rest )
124 when "child"
125 elements.each do |element|
126 matches |= filter( element.to_a, rest ) if element.kind_of? Element
127 end
128 when "attribute"
129 elements.each do |element|
130 matches << element.attributes[ rest ] if element.kind_of? Element
131 end
132 when "parent"
133 matches = filter(elements.collect{|element| element.parent}.uniq, rest)
134 when "following-sibling"
135 matches = filter(elements.collect{|element| element.next_sibling}.uniq,
136 rest)
137 when "previous-sibling"
138 matches = filter(elements.collect{|element|
139 element.previous_sibling}.uniq, rest )
140 end
141 return matches.uniq
142 end
143
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
151 # not included.
152 #
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 )
161 ind = 1
162 bcount = 1
163 while bcount > 0
164 bcount += 1 if path[ind] == ?[
165 bcount -= 1 if path[ind] == ?]
166 ind += 1
167 end
168 ind -= 1
169 predicate = path[1..ind-1]
170 rest = path[ind+1..-1]
171
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"
175 }
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 ) {
180 "attribute(\"#$1\")"
181 }
182 predicate.gsub!( /\bmod\b/u, "%" )
183 predicate.gsub!( /\b(\w[-\w.]*\()/u ) {
184 fname = $1
185 fname.gsub( /-/u, "_" )
186 }
187
188 Functions.pair = [ 0, elements.size ]
189 results = []
190 elements.each do |element|
191 Functions.pair[0] += 1
192 Functions.node = element
193 res = eval( predicate )
194 case res
195 when true
196 results << element
197 when Fixnum
198 results << element if Functions.pair[0] == res
199 when String
200 results << element
201 end
202 end
203 return filter( results, rest )
204 end
205
206 def QuickPath::attribute( name )
207 return Functions.node.attributes[name] if Functions.node.kind_of? Element
208 end
209
210 def QuickPath::name()
211 return Functions.node.name if Functions.node.kind_of? Element
212 end
213
214 def QuickPath::method_missing( id, *args )
215 begin
216 Functions.send( id.id2name, *args )
217 rescue Exception
218 raise "METHOD: #{id.id2name}(#{args.join ', '})\n#{$!.message}"
219 end
220 end
221
222 def QuickPath::function( elements, fname, rest )
223 args = parse_args( elements, rest )
224 Functions.pair = [0, elements.size]
225 results = []
226 elements.each do |element|
227 Functions.pair[0] += 1
228 Functions.node = element
229 res = Functions.send( fname, *args )
230 case res
231 when true
232 results << element
233 when Fixnum
234 results << element if Functions.pair[0] == res
235 end
236 end
237 return results
238 end
239
240 def QuickPath::parse_args( element, string )
241 # /.*?(?:\)|,)/
242 arguments = []
243 buffer = ""
244 while string and string != ""
245 c = string[0]
246 string.sub!(/^./u, "")
247 case c
248 when ?,
249 # if depth = 1, then we start a new argument
250 arguments << evaluate( buffer )
251 #arguments << evaluate( string[0..count] )
252 when ?(
253 # start a new method call
254 function( element, buffer, string )
255 buffer = ""
256 when ?)
257 # close the method call and return arguments
258 return arguments
259 else
260 buffer << c
261 end
262 end
263 ""
264 end
265 end
266end
Note: See TracBrowser for help on using the repository browser.