summaryrefslogtreecommitdiff
path: root/lib/rexml/functions.rb
blob: d64ba7e378fa6200b3e7b99914fdfa76a0ee4c6f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
module REXML
	# If you add a method, keep in mind two things:
	# (1) the first argument will always be a list of nodes from which to
	# filter.  In the case of context methods (such as position), the function
	# should return an array with a value for each child in the array.
	# (2) all method calls from XML will have "-" replaced with "_".
	# Therefore, in XML, "local-name()" is identical (and actually becomes)
	# "local_name()"
	module Functions
		@@node = nil
    @@index = nil
    @@size = nil
		@@variables = {}
		@@namespace_context = {}

		def Functions::node=(value); @@node = value; end
		def Functions::index=(value); @@index = value; end
		def Functions::size=(value); @@size = value; end
		def Functions::variables=(value); @@variables = value; end
		def Functions::namespace_context=(value)
			@@namespace_context = value
		end
		def Functions::node; @@node; end
		def Functions::index; @@index; end
		def Functions::size; @@size; end
		def Functions::variables; @@variables; end
		def Functions::namespace_context; @@namespace_context; end

		def Functions::text( )
			if @@node.node_type == :element
				return @@node.text
			elsif @@node.node_type == :text
				return @@node.value
			else
				return false
			end
		end

		def Functions::last( )
			@@size
		end

		def Functions::position( )
			@@index
		end

		def Functions::count( node_set )
			node_set.size
		end

		# Since REXML is non-validating, this method is not implemented as it
		# requires a DTD
		def Functions::id( object )
		end

		# UNTESTED
		def Functions::local_name( node_set=nil )
			get_namespace( node_set ) do |node|
				return node.local_name 
			end
		end

		def Functions::namespace_uri( node_set=nil )
			get_namespace( node_set ) {|node| node.namespace}
		end

		def Functions::name( node_set=nil )
			get_namespace( node_set ) do |node| 
				node.expanded_name
			end
		end

		# Helper method.
		def Functions::get_namespace( node_set = nil )
			if node_set == nil
				yield @@node if defined? @@node.namespace
			else	
				if node_set.namespace
					yield node_set
				else
					return unless node_set.kind_of? Enumerable
					node_set.each { |node| yield node if defined? node.namespace }
				end
			end
		end

		# A node-set is converted to a string by returning the string-value of the
		# node in the node-set that is first in document order. If the node-set is
		# empty, an empty string is returned.
		#
		# A number is converted to a string as follows
		#
		# NaN is converted to the string NaN 
		#
		# positive zero is converted to the string 0 
		#
		# negative zero is converted to the string 0 
		#
		# positive infinity is converted to the string Infinity 
		#
		# negative infinity is converted to the string -Infinity 
		#
		# if the number is an integer, the number is represented in decimal form
		# as a Number with no decimal point and no leading zeros, preceded by a
		# minus sign (-) if the number is negative
		#
		# otherwise, the number is represented in decimal form as a Number
		# including a decimal point with at least one digit before the decimal
		# point and at least one digit after the decimal point, preceded by a
		# minus sign (-) if the number is negative; there must be no leading zeros
		# before the decimal point apart possibly from the one required digit
		# immediately before the decimal point; beyond the one required digit
		# after the decimal point there must be as many, but only as many, more
		# digits as are needed to uniquely distinguish the number from all other
		# IEEE 754 numeric values.
		#
		# The boolean false value is converted to the string false. The boolean
		# true value is converted to the string true.
		#
		# An object of a type other than the four basic types is converted to a
		# string in a way that is dependent on that type.
		def Functions::string( object=nil )
			#object = @context unless object
			if object.instance_of? Array
				string( object[0] )
			elsif defined? object.node_type
				if object.node_type == :attribute
					object.value
				elsif object.node_type == :element
					object.text
				else
					object.to_s
				end
			else
				object.to_s
			end
		end

		# UNTESTED
		def Functions::concat( *objects )
			objects.join
		end

		# Fixed by Mike Stok
		def Functions::starts_with( string, test )
			string(string).index(string(test)) == 0
		end

		# Fixed by Mike Stok
		def Functions::contains( string, test )
			string(string).include? string(test)
		end

		# Kouhei fixed this 
		def Functions::substring_before( string, test )
			ruby_string = string(string)
			ruby_index = ruby_string.index(string(test))
			if ruby_index.nil?
				""
			else
				ruby_string[ 0...ruby_index ]
			end
		end
 
		# Kouhei fixed this too
		def Functions::substring_after( string, test )
			ruby_string = string(string)
			ruby_index = ruby_string.index(string(test))
			if ruby_index.nil?
				""
			else
				ruby_string[ ruby_index+1..-1 ]
			end
		end

		# Take equal portions of Mike Stok and Sean Russell; mix 
		# vigorously, and pour into a tall, chilled glass.  Serves 10,000.
		def Functions::substring( string, start, length=nil )
			ruby_string = string(string)
			ruby_length = if length.nil? 
											ruby_string.length.to_f
										else
											number(length)
										end
			ruby_start = number(start)

			# Handle the special cases
			return '' if (
				ruby_length.nan? or 
				ruby_start.nan? or
				ruby_start.infinite?
			)

			infinite_length = ruby_length.infinite? == 1
			ruby_length = ruby_string.length if infinite_length
				
			# Now, get the bounds.  The XPath bounds are 1..length; the ruby bounds 
			# are 0..length.  Therefore, we have to offset the bounds by one.
			ruby_start = ruby_start.round - 1
			ruby_length = ruby_length.round

			if ruby_start < 0
			 ruby_length += ruby_start unless infinite_length
			 ruby_start = 0
			end
			return '' if ruby_length <= 0
			ruby_string[ruby_start,ruby_length]
		end

		# UNTESTED
		def Functions::string_length( string )
			string(string).length
		end

		# UNTESTED
		def Functions::normalize_space( string=nil )
			string = string(@@node) if string.nil?
			if string.kind_of? Array
				string.collect{|x| string.to_s.strip.gsub(/\s+/um, ' ') if string}
			else
				string.to_s.strip.gsub(/\s+/um, ' ')
			end
		end

		# This is entirely Mike Stok's beast
		def Functions::translate( string, tr1, tr2 )
			from = string(tr1)
			to = string(tr2)

			# the map is our translation table.
			#
			# if a character occurs more than once in the
			# from string then we ignore the second &
			# subsequent mappings
			#
			# if a charactcer maps to nil then we delete it
			# in the output.  This happens if the from
			# string is longer than the to string
			#
			# there's nothing about - or ^ being special in
			# http://www.w3.org/TR/xpath#function-translate
			# so we don't build ranges or negated classes

			map = Hash.new
			0.upto(from.length - 1) { |pos|
				from_char = from[pos]
				unless map.has_key? from_char
					map[from_char] = 
					if pos < to.length
						to[pos]
					else
						nil
					end
				end
			}

			string(string).unpack('U*').collect { |c|
				if map.has_key? c then map[c] else c end
			}.compact.pack('U*')
		end

		# UNTESTED
		def Functions::boolean( object=nil )
			if object.kind_of? String
				if object =~ /\d+/u
					return object.to_f != 0
				else
					return object.size > 0
				end
			elsif object.kind_of? Array
				object = object.find{|x| x and true}
			end
			return object ? true : false
		end

		# UNTESTED
		def Functions::not( object )
			not boolean( object )
		end

		# UNTESTED
		def Functions::true( )
			true
		end

		# UNTESTED
		def Functions::false(  )
			false
		end

		# UNTESTED
		def Functions::lang( language )
			lang = false
			node = @@node
			attr = nil
			until node.nil?
				if node.node_type == :element
					attr = node.attributes["xml:lang"]
					unless attr.nil?
						lang = compare_language(string(language), attr)
						break
					else
					end
				end
				node = node.parent
			end
			lang
		end

		def Functions::compare_language lang1, lang2
			lang2.downcase.index(lang1.downcase) == 0
		end

		# a string that consists of optional whitespace followed by an optional
		# minus sign followed by a Number followed by whitespace is converted to
		# the IEEE 754 number that is nearest (according to the IEEE 754
		# round-to-nearest rule) to the mathematical value represented by the
		# string; any other string is converted to NaN
		#
		# boolean true is converted to 1; boolean false is converted to 0
		#
		# a node-set is first converted to a string as if by a call to the string
		# function and then converted in the same way as a string argument
		#
		# an object of a type other than the four basic types is converted to a
		# number in a way that is dependent on that type
		def Functions::number( object=nil )
			object = @@node unless object
			if object == true
				Float(1)
			elsif object == false
				Float(0)
			elsif object.kind_of? Array
				string( object ).to_f
			elsif object.kind_of? Float
				object
			else
				object.to_s.to_f
			end
		end

		def Functions::sum( nodes )
		end
		
		def Functions::floor( number )
			number(number).floor
		end

		def Functions::ceiling( number )
			number(number).ceil
		end

		def Functions::round( number )
			begin
				number(number).round
			rescue FloatDomainError
				number(number)
			end
		end

		def Functions::method_missing( id )
			puts "METHOD MISSING #{id.id2name}"
			XPath.match( @@node, id.id2name )
		end
	end
end