File: rexml/functions.rb

Overview
Module Structure
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: REXML#1
  module: Functions#9
has properties
module method: namespace_context= / 1 #14
module method: variables= / 1 #15
module method: namespace_context #16
module method: variables #17
module method: context= / 1 #19
module method: text / 1 #21
module method: last / 1 #31
module method: position / 1 #35
module method: count / 1 #39
module method: id / 1 #45
module method: local_name / 1 #49
module method: namespace_uri / 1 #55
module method: name / 1 #59
module method: get_namespace / 1 #66
module method: string / 1 #113
module method: string_value / 1 #132
module method: concat / 1 #145
module method: starts_with / 2 #150
module method: contains / 2 #155
module method: substring_before / 2 #160
module method: substring_after / 2 #171
module method: substring / 3 #180
module method: string_length / 1 #213
module method: normalize_space / 1 #218
module method: translate / 3 #228
module method: boolean / 1 #265
module method: not / 1 #279
module method: true / 1 #284
module method: false / 1 #289
module method: lang / 1 #294
module method: compare_language #312
module method: number / 1 #329
module method: sum / 1 #352
module method: floor / 1 #357
module method: ceiling / 1 #361
module method: round / 1 #365
module method: processing_instruction / 1 #373
module method: method_missing / 1 #377

Code

   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 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
 382  end