1 | module REXML
|
---|
2 | # If you add a method, keep in mind two things:
|
---|
3 | # (1) the first argument will always be a list of nodes from which to
|
---|
4 | # filter. In the case of context methods (such as position), the function
|
---|
5 | # should return an array with a value for each child in the array.
|
---|
6 | # (2) all method calls from XML will have "-" replaced with "_".
|
---|
7 | # Therefore, in XML, "local-name()" is identical (and actually becomes)
|
---|
8 | # "local_name()"
|
---|
9 | module Functions
|
---|
10 | @@context = nil
|
---|
11 | @@namespace_context = {}
|
---|
12 | @@variables = {}
|
---|
13 |
|
---|
14 | def Functions::namespace_context=(x) ; @@namespace_context=x ; end
|
---|
15 | def Functions::variables=(x) ; @@variables=x ; end
|
---|
16 | def Functions::namespace_context ; @@namespace_context ; end
|
---|
17 | def Functions::variables ; @@variables ; end
|
---|
18 |
|
---|
19 | def Functions::context=(value); @@context = value; end
|
---|
20 |
|
---|
21 | def Functions::text( )
|
---|
22 | if @@context[:node].node_type == :element
|
---|
23 | return @@context[:node].find_all{|n| n.node_type == :text}.collect{|n| n.value}
|
---|
24 | elsif @@context[:node].node_type == :text
|
---|
25 | return @@context[:node].value
|
---|
26 | else
|
---|
27 | return false
|
---|
28 | end
|
---|
29 | end
|
---|
30 |
|
---|
31 | def Functions::last( )
|
---|
32 | @@context[:size]
|
---|
33 | end
|
---|
34 |
|
---|
35 | def Functions::position( )
|
---|
36 | @@context[:index]
|
---|
37 | end
|
---|
38 |
|
---|
39 | def Functions::count( node_set )
|
---|
40 | node_set.size
|
---|
41 | end
|
---|
42 |
|
---|
43 | # Since REXML is non-validating, this method is not implemented as it
|
---|
44 | # requires a DTD
|
---|
45 | def Functions::id( object )
|
---|
46 | end
|
---|
47 |
|
---|
48 | # UNTESTED
|
---|
49 | def Functions::local_name( node_set=nil )
|
---|
50 | get_namespace( node_set ) do |node|
|
---|
51 | return node.local_name
|
---|
52 | end
|
---|
53 | end
|
---|
54 |
|
---|
55 | def Functions::namespace_uri( node_set=nil )
|
---|
56 | get_namespace( node_set ) {|node| node.namespace}
|
---|
57 | end
|
---|
58 |
|
---|
59 | def Functions::name( node_set=nil )
|
---|
60 | get_namespace( node_set ) do |node|
|
---|
61 | node.expanded_name
|
---|
62 | end
|
---|
63 | end
|
---|
64 |
|
---|
65 | # Helper method.
|
---|
66 | def Functions::get_namespace( node_set = nil )
|
---|
67 | if node_set == nil
|
---|
68 | yield @@context[:node] if defined? @@context[:node].namespace
|
---|
69 | else
|
---|
70 | if node_set.respond_to? :each
|
---|
71 | node_set.each { |node| yield node if defined? node.namespace }
|
---|
72 | elsif node_set.respond_to? :namespace
|
---|
73 | yield node_set
|
---|
74 | end
|
---|
75 | end
|
---|
76 | end
|
---|
77 |
|
---|
78 | # A node-set is converted to a string by returning the string-value of the
|
---|
79 | # node in the node-set that is first in document order. If the node-set is
|
---|
80 | # empty, an empty string is returned.
|
---|
81 | #
|
---|
82 | # A number is converted to a string as follows
|
---|
83 | #
|
---|
84 | # NaN is converted to the string NaN
|
---|
85 | #
|
---|
86 | # positive zero is converted to the string 0
|
---|
87 | #
|
---|
88 | # negative zero is converted to the string 0
|
---|
89 | #
|
---|
90 | # positive infinity is converted to the string Infinity
|
---|
91 | #
|
---|
92 | # negative infinity is converted to the string -Infinity
|
---|
93 | #
|
---|
94 | # if the number is an integer, the number is represented in decimal form
|
---|
95 | # as a Number with no decimal point and no leading zeros, preceded by a
|
---|
96 | # minus sign (-) if the number is negative
|
---|
97 | #
|
---|
98 | # otherwise, the number is represented in decimal form as a Number
|
---|
99 | # including a decimal point with at least one digit before the decimal
|
---|
100 | # point and at least one digit after the decimal point, preceded by a
|
---|
101 | # minus sign (-) if the number is negative; there must be no leading zeros
|
---|
102 | # before the decimal point apart possibly from the one required digit
|
---|
103 | # immediately before the decimal point; beyond the one required digit
|
---|
104 | # after the decimal point there must be as many, but only as many, more
|
---|
105 | # digits as are needed to uniquely distinguish the number from all other
|
---|
106 | # IEEE 754 numeric values.
|
---|
107 | #
|
---|
108 | # The boolean false value is converted to the string false. The boolean
|
---|
109 | # true value is converted to the string true.
|
---|
110 | #
|
---|
111 | # An object of a type other than the four basic types is converted to a
|
---|
112 | # string in a way that is dependent on that type.
|
---|
113 | def Functions::string( object=nil )
|
---|
114 | #object = @context unless object
|
---|
115 | if object.instance_of? Array
|
---|
116 | string( object[0] )
|
---|
117 | elsif defined? object.node_type
|
---|
118 | if object.node_type == :attribute
|
---|
119 | object.value
|
---|
120 | elsif object.node_type == :element || object.node_type == :document
|
---|
121 | string_value(object)
|
---|
122 | else
|
---|
123 | object.to_s
|
---|
124 | end
|
---|
125 | elsif object.nil?
|
---|
126 | return ""
|
---|
127 | else
|
---|
128 | object.to_s
|
---|
129 | end
|
---|
130 | end
|
---|
131 |
|
---|
132 | def Functions::string_value( o )
|
---|
133 | rv = ""
|
---|
134 | o.children.each { |e|
|
---|
135 | if e.node_type == :text
|
---|
136 | rv << e.to_s
|
---|
137 | elsif e.node_type == :element
|
---|
138 | rv << string_value( e )
|
---|
139 | end
|
---|
140 | }
|
---|
141 | rv
|
---|
142 | end
|
---|
143 |
|
---|
144 | # UNTESTED
|
---|
145 | def Functions::concat( *objects )
|
---|
146 | objects.join
|
---|
147 | end
|
---|
148 |
|
---|
149 | # Fixed by Mike Stok
|
---|
150 | def Functions::starts_with( string, test )
|
---|
151 | string(string).index(string(test)) == 0
|
---|
152 | end
|
---|
153 |
|
---|
154 | # Fixed by Mike Stok
|
---|
155 | def Functions::contains( string, test )
|
---|
156 | string(string).include?(string(test))
|
---|
157 | end
|
---|
158 |
|
---|
159 | # Kouhei fixed this
|
---|
160 | def Functions::substring_before( string, test )
|
---|
161 | ruby_string = string(string)
|
---|
162 | ruby_index = ruby_string.index(string(test))
|
---|
163 | if ruby_index.nil?
|
---|
164 | ""
|
---|
165 | else
|
---|
166 | ruby_string[ 0...ruby_index ]
|
---|
167 | end
|
---|
168 | end
|
---|
169 |
|
---|
170 | # Kouhei fixed this too
|
---|
171 | def Functions::substring_after( string, test )
|
---|
172 | ruby_string = string(string)
|
---|
173 | test_string = string(test)
|
---|
174 | return $1 if ruby_string =~ /#{test}(.*)/
|
---|
175 | ""
|
---|
176 | end
|
---|
177 |
|
---|
178 | # Take equal portions of Mike Stok and Sean Russell; mix
|
---|
179 | # vigorously, and pour into a tall, chilled glass. Serves 10,000.
|
---|
180 | def Functions::substring( string, start, length=nil )
|
---|
181 | ruby_string = string(string)
|
---|
182 | ruby_length = if length.nil?
|
---|
183 | ruby_string.length.to_f
|
---|
184 | else
|
---|
185 | number(length)
|
---|
186 | end
|
---|
187 | ruby_start = number(start)
|
---|
188 |
|
---|
189 | # Handle the special cases
|
---|
190 | return '' if (
|
---|
191 | ruby_length.nan? or
|
---|
192 | ruby_start.nan? or
|
---|
193 | ruby_start.infinite?
|
---|
194 | )
|
---|
195 |
|
---|
196 | infinite_length = ruby_length.infinite? == 1
|
---|
197 | ruby_length = ruby_string.length if infinite_length
|
---|
198 |
|
---|
199 | # Now, get the bounds. The XPath bounds are 1..length; the ruby bounds
|
---|
200 | # are 0..length. Therefore, we have to offset the bounds by one.
|
---|
201 | ruby_start = ruby_start.round - 1
|
---|
202 | ruby_length = ruby_length.round
|
---|
203 |
|
---|
204 | if ruby_start < 0
|
---|
205 | ruby_length += ruby_start unless infinite_length
|
---|
206 | ruby_start = 0
|
---|
207 | end
|
---|
208 | return '' if ruby_length <= 0
|
---|
209 | ruby_string[ruby_start,ruby_length]
|
---|
210 | end
|
---|
211 |
|
---|
212 | # UNTESTED
|
---|
213 | def Functions::string_length( string )
|
---|
214 | string(string).length
|
---|
215 | end
|
---|
216 |
|
---|
217 | # UNTESTED
|
---|
218 | def Functions::normalize_space( string=nil )
|
---|
219 | string = string(@@context[:node]) if string.nil?
|
---|
220 | if string.kind_of? Array
|
---|
221 | string.collect{|x| string.to_s.strip.gsub(/\s+/um, ' ') if string}
|
---|
222 | else
|
---|
223 | string.to_s.strip.gsub(/\s+/um, ' ')
|
---|
224 | end
|
---|
225 | end
|
---|
226 |
|
---|
227 | # This is entirely Mike Stok's beast
|
---|
228 | def Functions::translate( string, tr1, tr2 )
|
---|
229 | from = string(tr1)
|
---|
230 | to = string(tr2)
|
---|
231 |
|
---|
232 | # the map is our translation table.
|
---|
233 | #
|
---|
234 | # if a character occurs more than once in the
|
---|
235 | # from string then we ignore the second &
|
---|
236 | # subsequent mappings
|
---|
237 | #
|
---|
238 | # if a charactcer maps to nil then we delete it
|
---|
239 | # in the output. This happens if the from
|
---|
240 | # string is longer than the to string
|
---|
241 | #
|
---|
242 | # there's nothing about - or ^ being special in
|
---|
243 | # http://www.w3.org/TR/xpath#function-translate
|
---|
244 | # so we don't build ranges or negated classes
|
---|
245 |
|
---|
246 | map = Hash.new
|
---|
247 | 0.upto(from.length - 1) { |pos|
|
---|
248 | from_char = from[pos]
|
---|
249 | unless map.has_key? from_char
|
---|
250 | map[from_char] =
|
---|
251 | if pos < to.length
|
---|
252 | to[pos]
|
---|
253 | else
|
---|
254 | nil
|
---|
255 | end
|
---|
256 | end
|
---|
257 | }
|
---|
258 |
|
---|
259 | string(string).unpack('U*').collect { |c|
|
---|
260 | if map.has_key? c then map[c] else c end
|
---|
261 | }.compact.pack('U*')
|
---|
262 | end
|
---|
263 |
|
---|
264 | # UNTESTED
|
---|
265 | def Functions::boolean( object=nil )
|
---|
266 | if object.kind_of? String
|
---|
267 | if object =~ /\d+/u
|
---|
268 | return object.to_f != 0
|
---|
269 | else
|
---|
270 | return object.size > 0
|
---|
271 | end
|
---|
272 | elsif object.kind_of? Array
|
---|
273 | object = object.find{|x| x and true}
|
---|
274 | end
|
---|
275 | return object ? true : false
|
---|
276 | end
|
---|
277 |
|
---|
278 | # UNTESTED
|
---|
279 | def Functions::not( object )
|
---|
280 | not boolean( object )
|
---|
281 | end
|
---|
282 |
|
---|
283 | # UNTESTED
|
---|
284 | def Functions::true( )
|
---|
285 | true
|
---|
286 | end
|
---|
287 |
|
---|
288 | # UNTESTED
|
---|
289 | def Functions::false( )
|
---|
290 | false
|
---|
291 | end
|
---|
292 |
|
---|
293 | # UNTESTED
|
---|
294 | def Functions::lang( language )
|
---|
295 | lang = false
|
---|
296 | node = @@context[:node]
|
---|
297 | attr = nil
|
---|
298 | until node.nil?
|
---|
299 | if node.node_type == :element
|
---|
300 | attr = node.attributes["xml:lang"]
|
---|
301 | unless attr.nil?
|
---|
302 | lang = compare_language(string(language), attr)
|
---|
303 | break
|
---|
304 | else
|
---|
305 | end
|
---|
306 | end
|
---|
307 | node = node.parent
|
---|
308 | end
|
---|
309 | lang
|
---|
310 | end
|
---|
311 |
|
---|
312 | def Functions::compare_language lang1, lang2
|
---|
313 | lang2.downcase.index(lang1.downcase) == 0
|
---|
314 | end
|
---|
315 |
|
---|
316 | # a string that consists of optional whitespace followed by an optional
|
---|
317 | # minus sign followed by a Number followed by whitespace is converted to
|
---|
318 | # the IEEE 754 number that is nearest (according to the IEEE 754
|
---|
319 | # round-to-nearest rule) to the mathematical value represented by the
|
---|
320 | # string; any other string is converted to NaN
|
---|
321 | #
|
---|
322 | # boolean true is converted to 1; boolean false is converted to 0
|
---|
323 | #
|
---|
324 | # a node-set is first converted to a string as if by a call to the string
|
---|
325 | # function and then converted in the same way as a string argument
|
---|
326 | #
|
---|
327 | # an object of a type other than the four basic types is converted to a
|
---|
328 | # number in a way that is dependent on that type
|
---|
329 | def Functions::number( object=nil )
|
---|
330 | object = @@context[:node] unless object
|
---|
331 | case object
|
---|
332 | when true
|
---|
333 | Float(1)
|
---|
334 | when false
|
---|
335 | Float(0)
|
---|
336 | when Array
|
---|
337 | number(string( object ))
|
---|
338 | when Numeric
|
---|
339 | object.to_f
|
---|
340 | else
|
---|
341 | str = string( object )
|
---|
342 | #puts "STRING OF #{object.inspect} = #{str}"
|
---|
343 | # If XPath ever gets scientific notation...
|
---|
344 | #if str =~ /^\s*-?(\d*\.?\d+|\d+\.)([Ee]\d*)?\s*$/
|
---|
345 | if str =~ /^\s*-?(\d*\.?\d+|\d+\.)\s*$/
|
---|
346 | str.to_f
|
---|
347 | else
|
---|
348 | (0.0 / 0.0)
|
---|
349 | end
|
---|
350 | end
|
---|
351 | end
|
---|
352 |
|
---|
353 | def Functions::sum( nodes )
|
---|
354 | nodes = [nodes] unless nodes.kind_of? Array
|
---|
355 | nodes.inject(0) { |r,n| r += number(string(n)) }
|
---|
356 | end
|
---|
357 |
|
---|
358 | def Functions::floor( number )
|
---|
359 | number(number).floor
|
---|
360 | end
|
---|
361 |
|
---|
362 | def Functions::ceiling( number )
|
---|
363 | number(number).ceil
|
---|
364 | end
|
---|
365 |
|
---|
366 | def Functions::round( number )
|
---|
367 | begin
|
---|
368 | number(number).round
|
---|
369 | rescue FloatDomainError
|
---|
370 | number(number)
|
---|
371 | end
|
---|
372 | end
|
---|
373 |
|
---|
374 | def Functions::processing_instruction( node )
|
---|
375 | node.node_type == :processing_instruction
|
---|
376 | end
|
---|
377 |
|
---|
378 | def Functions::method_missing( id )
|
---|
379 | puts "METHOD MISSING #{id.id2name}"
|
---|
380 | XPath.match( @@context[:node], id.id2name )
|
---|
381 | end
|
---|
382 | end
|
---|
383 | end
|
---|