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