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
50
51
52
53
54
55
56
57
58
59
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
265
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
279 obj = list.get(0);
280 }
281
282 if (nav != null && (nav.isElement(obj) || nav.isDocument(obj)))
283 {
284 Iterator descendantAxisIterator = nav.getDescendantAxisIterator(obj);
285 StringBuffer sb = new StringBuffer();
286 while (descendantAxisIterator.hasNext())
287 {
288 Object descendant = descendantAxisIterator.next();
289 if (nav.isText(descendant))
290 {
291 sb.append(nav.getTextStringValue(descendant));
292 }
293 }
294 return sb.toString();
295 }
296 else if (nav != null && nav.isAttribute(obj))
297 {
298 return nav.getAttributeStringValue(obj);
299 }
300 else if (nav != null && nav.isText(obj))
301 {
302 return nav.getTextStringValue(obj);
303 }
304 else if (nav != null && nav.isProcessingInstruction(obj))
305 {
306 return nav.getProcessingInstructionData(obj);
307 }
308 else if (nav != null && nav.isComment(obj))
309 {
310 return nav.getCommentStringValue(obj);
311 }
312 else if (nav != null && nav.isNamespace(obj))
313 {
314 return nav.getNamespaceStringValue(obj);
315 }
316 else if (obj instanceof String)
317 {
318 return (String) obj;
319 }
320 else if (obj instanceof Boolean)
321 {
322 return stringValue(((Boolean) obj).booleanValue());
323 }
324 else if (obj instanceof Number)
325 {
326 return stringValue(((Number) obj).doubleValue());
327 }
328 return "";
329 }
330 catch (UnsupportedAxisException e)
331 {
332 throw new JaxenRuntimeException(e);
333 }
334
335 }
336
337 private static String stringValue(double value)
338 {
339
340
341
342 if (value == 0) return "0";
343
344
345 String result = null;
346 synchronized (format) {
347 result = format.format(value);
348 }
349 return result;
350
351 }
352
353 private static String stringValue(boolean value)
354 {
355 return value ? "true" : "false";
356 }
357
358 }