PageRenderTime 50ms CodeModel.GetById 12ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 1ms

/tools/Ruby/lib/ruby/1.8/rexml/functions.rb

http://github.com/agross/netopenspace
Ruby | 382 lines | 247 code | 42 blank | 93 comment | 42 complexity | f08943bf9c60a4d4099111eea878c94e MD5 | raw file
  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    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 character 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        # If XPath ever gets scientific notation...
343        #if str =~ /^\s*-?(\d*\.?\d+|\d+\.)([Ee]\d*)?\s*$/
344        if str =~ /^\s*-?(\d*\.?\d+|\d+\.)\s*$/
345          str.to_f
346        else
347          (0.0 / 0.0)
348        end
349      end
350    end
351
352    def Functions::sum( nodes )
353      nodes = [nodes] unless nodes.kind_of? Array
354      nodes.inject(0) { |r,n| r += number(string(n)) }
355    end
356    
357    def Functions::floor( number )
358      number(number).floor
359    end
360
361    def Functions::ceiling( number )
362      number(number).ceil
363    end
364
365    def Functions::round( number )
366      begin
367        number(number).round
368      rescue FloatDomainError
369        number(number)
370      end
371    end
372
373    def Functions::processing_instruction( node )
374      node.node_type == :processing_instruction
375    end
376
377    def Functions::method_missing( id )
378      puts "METHOD MISSING #{id.id2name}"
379      XPath.match( @@context[:node], id.id2name )
380    end
381  end
382end