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)
11
@@namespace_context = {}
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
19
def Functions::context=(value); @@context = value; end
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
31
def Functions::last( )
35
def Functions::position( )
39
def Functions::count( node_set )
43
# Since REXML is non-validating, this method is not implemented as it
45
def Functions::id( object )
49
def Functions::local_name( node_set=nil )
50
get_namespace( node_set ) do |node|
51
return node.local_name
55
def Functions::namespace_uri( node_set=nil )
56
get_namespace( node_set ) {|node| node.namespace}
59
def Functions::name( node_set=nil )
60
get_namespace( node_set ) do |node|
66
def Functions::get_namespace( node_set = nil )
68
yield @@context[:node] if defined? @@context[:node].namespace
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
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.
82
# A number is converted to a string as follows
84
# NaN is converted to the string NaN
86
# positive zero is converted to the string 0
88
# negative zero is converted to the string 0
90
# positive infinity is converted to the string Infinity
92
# negative infinity is converted to the string -Infinity
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
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.
108
# The boolean false value is converted to the string false. The boolean
109
# true value is converted to the string true.
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
117
elsif defined? object.node_type
118
if object.node_type == :attribute
120
elsif object.node_type == :element || object.node_type == :document
132
def Functions::string_value( o )
134
o.children.each { |e|
135
if e.node_type == :text
137
elsif e.node_type == :element
138
rv << string_value( e )
145
def Functions::concat( *objects )
150
def Functions::starts_with( string, test )
151
string(string).index(string(test)) == 0
155
def Functions::contains( string, test )
156
string(string).include?(string(test))
160
def Functions::substring_before( string, test )
161
ruby_string = string(string)
162
ruby_index = ruby_string.index(string(test))
166
ruby_string[ 0...ruby_index ]
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}(.*)/
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
187
ruby_start = number(start)
189
# Handle the special cases
196
infinite_length = ruby_length.infinite? == 1
197
ruby_length = ruby_string.length if infinite_length
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
205
ruby_length += ruby_start unless infinite_length
208
return '' if ruby_length <= 0
209
ruby_string[ruby_start,ruby_length]
213
def Functions::string_length( string )
214
string(string).length
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}
223
string.to_s.strip.gsub(/\s+/um, ' ')
227
# This is entirely Mike Stok's beast
228
def Functions::translate( string, tr1, tr2 )
232
# the map is our translation table.
234
# if a character occurs more than once in the
235
# from string then we ignore the second &
236
# subsequent mappings
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
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
247
0.upto(from.length - 1) { |pos|
248
from_char = from[pos]
249
unless map.has_key? from_char
259
string(string).unpack('U*').collect { |c|
260
if map.has_key? c then map[c] else c end
265
def Functions::boolean( object=nil )
266
if object.kind_of? String
268
return object.to_f != 0
270
return object.size > 0
272
elsif object.kind_of? Array
273
object = object.find{|x| x and true}
275
return object ? true : false
279
def Functions::not( object )
280
not boolean( object )
284
def Functions::true( )
289
def Functions::false( )
294
def Functions::lang( language )
296
node = @@context[:node]
299
if node.node_type == :element
300
attr = node.attributes["xml:lang"]
302
lang = compare_language(string(language), attr)
312
def Functions::compare_language lang1, lang2
313
lang2.downcase.index(lang1.downcase) == 0
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
322
# boolean true is converted to 1; boolean false is converted to 0
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
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
337
number(string( object ))
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*$/
353
def Functions::sum( nodes )
354
nodes = [nodes] unless nodes.kind_of? Array
355
nodes.inject(0) { |r,n| r += number(string(n)) }
358
def Functions::floor( number )
362
def Functions::ceiling( number )
366
def Functions::round( number )
369
rescue FloatDomainError
374
def Functions::processing_instruction( node )
375
node.node_type == :processing_instruction
378
def Functions::method_missing( id )
379
puts "METHOD MISSING #{id.id2name}"
380
XPath.match( @@context[:node], id.id2name )