View Javadoc

    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     private static DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH);
    201
    202     static {
    203         DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH);
    204         symbols.setNaN("NaN");
    205         symbols.setInfinity("Infinity");
    206         format.setGroupingUsed(false);
    207         format.setMaximumFractionDigits(32);
    208         format.setDecimalFormatSymbols(symbols);
    209     }
    210
    211     /***
    212      * Create a new <code>StringFunction</code> object.
    213      */
    214     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         int size = args.size();
    232
    233         if ( size == 0 )
    234         {
    235             return evaluate( context.getNodeSet(),
    236                              context.getNavigator() );
    237         }
    238         else if ( size == 1 )
    239         {
    240             return evaluate( args.get(0),
    241                              context.getNavigator() );
    242         }
    243
    244         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             if (obj == null) {
    261                 return "";
    262             }
    263
    264             // Workaround because XOM uses lists for Text nodes
    265             // so we need to check for that first
    266             if (nav != null && nav.isText(obj))
    267             {
    268                 return nav.getTextStringValue(obj);
    269             }
    270
    271             if (obj instanceof List)
    272             {
    273                 List list = (List) obj;
    274                 if (list.isEmpty())
    275                 {
    276                     return "";
    277                 }
    278                 // do not recurse: only first list should unwrap
    279                 obj = list.get(0);
    280             }
    281
    282             if (nav != null) {
    283                 // This stack of instanceof really suggests there's
    284                 // a failure to take adavantage of polymorphism here
    285                 if (nav.isElement(obj))
    286                 {
    287                     return nav.getElementStringValue(obj);
    288                 }
    289                 else if (nav.isAttribute(obj))
    290                 {
    291                     return nav.getAttributeStringValue(obj);
    292                 }
    293
    294                 else if (nav.isDocument(obj))
    295                 {
    296                     Iterator childAxisIterator = nav.getChildAxisIterator(obj);
    297                     while (childAxisIterator.hasNext())
    298                     {
    299                         Object descendant = childAxisIterator.next();
    300                         if (nav.isElement(descendant))
    301                         {
    302                             return nav.getElementStringValue(descendant);
    303                         }
    304                     }
    305                 }
    306                 else if (nav.isProcessingInstruction(obj))
    307                 {
    308                     return nav.getProcessingInstructionData(obj);
    309                 }
    310                 else if (nav.isComment(obj))
    311                 {
    312                     return nav.getCommentStringValue(obj);
    313                 }
    314                 else if (nav.isText(obj))
    315                 {
    316                     return nav.getTextStringValue(obj);
    317                 }
    318                 else if (nav.isNamespace(obj))
    319                 {
    320                     return nav.getNamespaceStringValue(obj);
    321                 }
    322             }
    323
    324             if (obj instanceof String)
    325             {
    326                 return (String) obj;
    327             }
    328             else if (obj instanceof Boolean)
    329             {
    330                 return stringValue(((Boolean) obj).booleanValue());
    331             }
    332             else if (obj instanceof Number)
    333             {
    334                 return stringValue(((Number) obj).doubleValue());
    335             }
    336
    337         }
    338         catch (UnsupportedAxisException e)
    339         {
    340             throw new JaxenRuntimeException(e);
    341         }
    342
    343         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         if (value == 0) return "0";
    353
    354         // need to synchronize object for thread-safety
    355         String result = null;
    356         synchronized (format) {
    357             result = format.format(value);
    358         }
    359         return result;
    360
    361     }
    362
    363     private static String stringValue(boolean value)
    364     {
    365         return value ? "true" : "false";
    366     }
    367
    368 }