1 /* 2 * $Header$ 3 * $Revision$ 4 * $Date$ 5 * 6 * ==================================================================== 7 * 8 * Copyright 2000-2002 bob mcwhirter & James Strachan. 9 * All rights reserved. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions are 13 * met: 14 * 15 * * Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 18 * * Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 22 * * Neither the name of the Jaxen Project nor the names of its 23 * contributors may be used to endorse or promote products derived 24 * from this software without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 27 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 29 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 30 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 31 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 32 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 33 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 34 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 35 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 36 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 * 38 * ==================================================================== 39 * This software consists of voluntary contributions made by many 40 * individuals on behalf of the Jaxen Project and was originally 41 * created by bob mcwhirter <bob@werken.com> and 42 * James Strachan <jstrachan@apache.org>. For more information on the 43 * Jaxen Project, please see <http://www.jaxen.org/>. 44 * 45 * $Id$ 46 */ 47 48 49 package org.jaxen.function; 50 51 import org.jaxen.Context; 52 import org.jaxen.Function; 53 import org.jaxen.FunctionCallException; 54 import org.jaxen.Navigator; 55 import org.jaxen.UnsupportedAxisException; 56 import org.jaxen.JaxenRuntimeException; 57 58 import java.text.DecimalFormat; 59 import java.text.NumberFormat; 60 import java.text.DecimalFormatSymbols; 61 import java.util.List; 62 import java.util.Iterator; 63 import java.util.Locale; 64 65 /** 66 * <p> 67 * <b>4.2</b> <code><i>string</i> string(<i>object</i>)</code> 68 * </p> 69 * 70 * 71 * <blockquote cite="http://www.w3.org/TR/xpath"> 72 * <p> 73 * The <b>string</b> function converts 74 * an object to a string as follows: 75 * </p> 76 * 77 * <ul> 78 * 79 * <li> 80 * <p> 81 * A node-set is converted to a string by returning the <a 82 * href="https://www.w3.org/TR/xpath#dt-string-value" target="_top">string-value</a> of the node in the node-set 83 * that is first in <a href="https://www.w3.org/TR/xpath#dt-document-order" target="_top">document order</a>. If 84 * the node-set is empty, an empty string is returned. 85 * </p> 86 * </li> 87 * 88 * <li> 89 * <p> 90 * A number is converted to a string as follows 91 * </p> 92 * 93 * <ul> 94 * 95 * <li> 96 * <p> 97 * NaN is converted to the string <code>NaN</code> 98 * </p> 99 * </li> 100 * 101 * <li> 102 * <p> 103 * positive zero is converted to the string <code>0</code> 104 * </p> 105 * </li> 106 * 107 * <li> 108 * 109 * <p> 110 * negative zero is converted to the string <code>0</code> 111 * </p> 112 * </li> 113 * 114 * <li> 115 * <p> 116 * positive infinity is converted to the string <code>Infinity</code> 117 * </p> 118 * </li> 119 * 120 * <li> 121 * <p> 122 * negative infinity is converted to the string <code>-Infinity</code> 123 * 124 * </p> 125 * </li> 126 * 127 * <li> 128 * <p> 129 * if the number is an integer, the number is represented in decimal 130 * form as a <a href="https://www.w3.org/TR/xpath#NT-Number" target="_top">Number</a> with no decimal point and 131 * no leading zeros, preceded by a minus sign (<code>-</code>) if 132 * the number is negative 133 * </p> 134 * </li> 135 * 136 * <li> 137 * <p> 138 * otherwise, the number is represented in decimal form as a Number including a decimal point with at least 139 * one digit before the decimal point and at least one digit after the 140 * decimal point, preceded by a minus sign (<code>-</code>) if the 141 * number is negative; there must be no leading zeros before the decimal 142 * point apart possibly from the one required digit immediately before 143 * the decimal point; beyond the one required digit after the decimal 144 * point there must be as many, but only as many, more digits as are 145 * needed to uniquely distinguish the number from all other IEEE 754 146 * numeric values. 147 * </p> 148 * 149 * </li> 150 * 151 * </ul> 152 * 153 * </li> 154 * 155 * <li> 156 * <p> 157 * The boolean false value is converted to the string <code>false</code>. 158 * The boolean true value is converted to the string <code>true</code>. 159 * </p> 160 * </li> 161 * 162 * <li> 163 * <p> 164 * An object of a type other than the four basic types is converted to a 165 * string in a way that is dependent on that type. 166 * </p> 167 * 168 * </li> 169 * 170 * </ul> 171 * 172 * <p> 173 * If the argument is omitted, it defaults to a node-set with the 174 * context node as its only member. 175 * </p> 176 * 177 * </blockquote> 178 * 179 * @author bob mcwhirter (bob @ werken.com) 180 * @see <a href="https://www.w3.org/TR/xpath#function-string" 181 * target="_top">Section 4.2 of the XPath Specification</a> 182 */ 183 public class StringFunction implements Function 184 { 185 186 private static DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH); 187 188 static { 189 DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH); 190 symbols.setNaN("NaN"); 191 symbols.setInfinity("Infinity"); 192 format.setGroupingUsed(false); 193 format.setMaximumFractionDigits(32); 194 format.setDecimalFormatSymbols(symbols); 195 } 196 197 /** 198 * Create a new <code>StringFunction</code> object. 199 */ 200 public StringFunction() {} 201 202 /** 203 * Returns the string-value of <code>args.get(0)</code> 204 * or of the context node if <code>args</code> is empty. 205 * 206 * @param context the context at the point in the 207 * expression where the function is called 208 * @param args list with zero or one element 209 * 210 * @return a <code>String</code> 211 * 212 * @throws FunctionCallException if <code>args</code> has more than one item 213 */ 214 public Object call(Context context, 215 List args) throws FunctionCallException 216 { 217 int size = args.size(); 218 219 if ( size == 0 ) 220 { 221 return evaluate( context.getNodeSet(), 222 context.getNavigator() ); 223 } 224 else if ( size == 1 ) 225 { 226 return evaluate( args.get(0), 227 context.getNavigator() ); 228 } 229 230 throw new FunctionCallException( "string() takes at most argument." ); 231 } 232 233 /** 234 * Returns the XPath string-value of <code>obj</code>. 235 * This operation is only defined if obj is a node, node-set, 236 * <code>String</code>, <code>Number</code>, or 237 * <code>Boolean</code>. For other types this function 238 * returns the empty string. 239 * 240 * @param obj the node, node-set, string, number, or boolean 241 * whose string-value is calculated 242 * @param nav the navigator used to calculate the string-value 243 * 244 * @return a <code>String</code>. May be empty but is never null. 245 */ 246 public static String evaluate(Object obj, 247 Navigator nav) 248 { 249 try 250 { 251 // Workaround because XOM uses lists for Text nodes 252 // so we need to check for that first 253 if (nav != null && nav.isText(obj)) 254 { 255 return nav.getTextStringValue(obj); 256 } 257 258 if (obj instanceof List) 259 { 260 List list = (List) obj; 261 if (list.isEmpty()) 262 { 263 return ""; 264 } 265 // do not recurse: only first list should unwrap 266 obj = list.get(0); 267 } 268 269 if (nav != null) { 270 // This stack of instanceof really suggests there's 271 // a failure to take advantage of polymorphism here 272 if (nav.isElement(obj)) 273 { 274 return nav.getElementStringValue(obj); 275 } 276 else if (nav.isAttribute(obj)) 277 { 278 return nav.getAttributeStringValue(obj); 279 } 280 281 else if (nav.isDocument(obj)) 282 { 283 Iterator childAxisIterator = nav.getChildAxisIterator(obj); 284 while (childAxisIterator.hasNext()) 285 { 286 Object descendant = childAxisIterator.next(); 287 if (nav.isElement(descendant)) 288 { 289 return nav.getElementStringValue(descendant); 290 } 291 } 292 } 293 else if (nav.isProcessingInstruction(obj)) 294 { 295 return nav.getProcessingInstructionData(obj); 296 } 297 else if (nav.isComment(obj)) 298 { 299 return nav.getCommentStringValue(obj); 300 } 301 else if (nav.isText(obj)) 302 { 303 return nav.getTextStringValue(obj); 304 } 305 else if (nav.isNamespace(obj)) 306 { 307 return nav.getNamespaceStringValue(obj); 308 } 309 } 310 311 if (obj instanceof String) 312 { 313 return (String) obj; 314 } 315 else if (obj instanceof Boolean) 316 { 317 return stringValue(((Boolean) obj).booleanValue()); 318 } 319 else if (obj instanceof Number) 320 { 321 return stringValue(((Number) obj).doubleValue()); 322 } 323 324 } 325 catch (UnsupportedAxisException e) 326 { 327 throw new JaxenRuntimeException(e); 328 } 329 330 return ""; 331 332 } 333 334 private static String stringValue(double value) 335 { 336 337 // DecimalFormat formats negative zero as "-0". 338 // Therefore we need to test for zero explicitly here. 339 if (value == 0) return "0"; 340 341 // need to synchronize object for thread-safety 342 String result = null; 343 synchronized (format) { 344 result = format.format(value); 345 } 346 return result; 347 348 } 349 350 private static String stringValue(boolean value) 351 { 352 return value ? "true" : "false"; 353 } 354 355 }