View Javadoc

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     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 }