~ubuntu-branches/ubuntu/hardy/ruby1.8/hardy-updates

« back to all changes in this revision

Viewing changes to lib/rexml/functions.rb

  • Committer: Bazaar Package Importer
  • Author(s): akira yamada
  • Date: 2007-03-13 22:11:58 UTC
  • mfrom: (1.1.5 upstream)
  • Revision ID: james.westby@ubuntu.com-20070313221158-h3oql37brlaf2go2
Tags: 1.8.6-1
* new upstream version, 1.8.6.
* libruby1.8 conflicts with libopenssl-ruby1.8 (< 1.8.6) (closes: #410018)
* changed packaging style to cdbs from dbs.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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