1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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
252
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
266 obj = list.get(0);
267 }
268
269 if (nav != null) {
270
271
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
338
339 if (value == 0) return "0";
340
341
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 }