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