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