Coverage Report - org.jaxen.function.StringFunction
 
Classes in this File Line Coverage Branch Coverage Complexity
StringFunction
93%
56/60
100%
20/20
8.4
 
 1  
 /*
 2  
  * $Header: /home/projects/jaxen/scm/jaxen/src/java/main/org/jaxen/function/StringFunction.java,v 1.33 2006/02/05 21:47:41 elharo Exp $
 3  
  * $Revision: 1.33 $
 4  
  * $Date: 2006/02/05 21:47:41 $
 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: StringFunction.java,v 1.33 2006/02/05 21:47:41 elharo Exp $
 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 src="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="http://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="http://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="http://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="http://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  104
     private static DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH);
 187  
     
 188  
     static {
 189  104
         DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH);
 190  104
         symbols.setNaN("NaN");
 191  104
         symbols.setInfinity("Infinity");
 192  104
         format.setGroupingUsed(false);
 193  104
         format.setMaximumFractionDigits(32);
 194  104
         format.setDecimalFormatSymbols(symbols);
 195  104
     }
 196  
 
 197  
     /**
 198  
      * Create a new <code>StringFunction</code> object.
 199  
      */
 200  106
     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  62
         int size = args.size();
 218  
 
 219  62
         if ( size == 0 )
 220  
         {
 221  40
             return evaluate( context.getNodeSet(),
 222  
                              context.getNavigator() );
 223  
         }
 224  22
         else if ( size == 1 )
 225  
         {
 226  20
             return evaluate( args.get(0),
 227  
                              context.getNavigator() );
 228  
         }
 229  
 
 230  2
         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  11370
             if (nav != null && nav.isText(obj))
 254  
             {
 255  68
                 return nav.getTextStringValue(obj);
 256  
             }
 257  
             
 258  11302
             if (obj instanceof List)
 259  
             {
 260  530
                 List list = (List) obj;
 261  530
                 if (list.isEmpty())
 262  
                 {
 263  16
                     return "";
 264  
                 }
 265  
                 // do not recurse: only first list should unwrap
 266  514
                 obj = list.get(0);
 267  
             }
 268  
             
 269  11286
             if (nav != null) {
 270  
                 // This stack of instanceof really suggests there's 
 271  
                 // a failure to take advantage of polymorphism here
 272  11284
                 if (nav.isElement(obj))
 273  
                 {
 274  1048
                     return nav.getElementStringValue(obj);
 275  
                 }
 276  10236
                 else if (nav.isAttribute(obj))
 277  
                 {
 278  2060
                     return nav.getAttributeStringValue(obj);
 279  
                 }
 280  
     
 281  8176
                 else if (nav.isDocument(obj))
 282  
                 {
 283  16
                     Iterator childAxisIterator = nav.getChildAxisIterator(obj);
 284  16
                     while (childAxisIterator.hasNext())
 285  
                     {
 286  16
                         Object descendant = childAxisIterator.next();
 287  16
                         if (nav.isElement(descendant))
 288  
                         {
 289  16
                             return nav.getElementStringValue(descendant);
 290  
                         }
 291  0
                     }
 292  0
                 }
 293  8160
                 else if (nav.isProcessingInstruction(obj))
 294  
                 {
 295  24
                     return nav.getProcessingInstructionData(obj);
 296  
                 }
 297  8136
                 else if (nav.isComment(obj))
 298  
                 {
 299  2
                     return nav.getCommentStringValue(obj);
 300  
                 }
 301  8134
                 else if (nav.isText(obj))
 302  
                 {
 303  40
                     return nav.getTextStringValue(obj);
 304  
                 }
 305  8094
                 else if (nav.isNamespace(obj))
 306  
                 {
 307  2
                     return nav.getNamespaceStringValue(obj);
 308  
                 }
 309  
             }
 310  
             
 311  8094
             if (obj instanceof String)
 312  
             {
 313  7564
                 return (String) obj;
 314  
             }
 315  530
             else if (obj instanceof Boolean)
 316  
             {
 317  288
                 return stringValue(((Boolean) obj).booleanValue());
 318  
             }
 319  242
             else if (obj instanceof Number)
 320  
             {
 321  238
                 return stringValue(((Number) obj).doubleValue());
 322  
             }
 323  
             
 324  
         }
 325  0
         catch (UnsupportedAxisException e)
 326  
         {
 327  0
             throw new JaxenRuntimeException(e);
 328  4
         }
 329  
         
 330  4
         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  238
         if (value == 0) return "0";
 340  
         
 341  
         // need to synchronize object for thread-safety
 342  220
         String result = null;
 343  220
         synchronized (format) {
 344  220
             result = format.format(value);
 345  220
         }
 346  220
         return result;
 347  
         
 348  
     }
 349  
 
 350  
     private static String stringValue(boolean value)
 351  
     {
 352  288
         return value ? "true" : "false";
 353  
     }
 354  
 
 355  
 }