2 Copyright (C) 2004 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
38 package gnu
.xml
.xpath
;
40 import java
.io
.IOException
;
41 import java
.text
.DecimalFormat
;
42 import java
.text
.DecimalFormatSymbols
;
43 import java
.util
.ArrayList
;
44 import java
.util
.Collection
;
45 import java
.util
.Collections
;
46 import java
.util
.Comparator
;
47 import java
.util
.HashSet
;
48 import java
.util
.Iterator
;
49 import java
.util
.List
;
50 import java
.util
.Locale
;
52 import java
.util
.StringTokenizer
;
53 import javax
.xml
.namespace
.QName
;
54 import javax
.xml
.parsers
.DocumentBuilder
;
55 import javax
.xml
.parsers
.DocumentBuilderFactory
;
56 import javax
.xml
.parsers
.ParserConfigurationException
;
57 import javax
.xml
.xpath
.XPathConstants
;
58 import javax
.xml
.xpath
.XPathExpression
;
59 import javax
.xml
.xpath
.XPathExpressionException
;
60 import org
.w3c
.dom
.Document
;
61 import org
.w3c
.dom
.Node
;
62 import org
.xml
.sax
.InputSource
;
63 import org
.xml
.sax
.SAXException
;
66 * An XPath expression.
67 * This can be evaluated in the context of a node to produce a result.
69 * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
71 public abstract class Expr
72 implements XPathExpression
75 protected static final Comparator documentOrderComparator
=
76 new DocumentOrderComparator();
78 protected static final DecimalFormat decimalFormat
=
79 new DecimalFormat("####################################################" +
80 ".####################################################",
81 new DecimalFormatSymbols(Locale
.US
));
83 public Object
evaluate(Object item
, QName returnType
)
84 throws XPathExpressionException
88 if (item
instanceof Node
)
90 context
= (Node
) item
;
91 ret
= evaluate(context
, 1, 1);
92 if (XPathConstants
.STRING
== returnType
&&
93 !(ret
instanceof String
))
95 ret
= _string(context
, ret
);
97 else if (XPathConstants
.NUMBER
== returnType
&&
98 !(ret
instanceof Double
))
100 ret
= new Double(_number(context
, ret
));
102 else if (XPathConstants
.BOOLEAN
== returnType
&&
103 !(ret
instanceof Boolean
))
105 ret
= _boolean(context
, ret
) ? Boolean
.TRUE
: Boolean
.FALSE
;
107 else if (XPathConstants
.NODE
== returnType
)
109 if (ret
instanceof Collection
)
111 Collection ns
= (Collection
) ret
;
118 ret
= (Node
) ns
.iterator().next();
121 throw new XPathExpressionException("multiple nodes in node-set");
124 else if (ret
!= null)
126 throw new XPathExpressionException("return value is not a node-set");
129 else if (XPathConstants
.NODESET
== returnType
)
131 if (ret
!= null && !(ret
instanceof Collection
))
133 throw new XPathExpressionException("return value is not a node-set");
140 public String
evaluate(Object item
)
141 throws XPathExpressionException
143 return (String
) evaluate(item
, XPathConstants
.STRING
);
146 public Object
evaluate(InputSource source
, QName returnType
)
147 throws XPathExpressionException
151 DocumentBuilderFactory factory
=
152 new gnu
.xml
.dom
.JAXPFactory();
153 DocumentBuilder builder
= factory
.newDocumentBuilder();
154 Document doc
= builder
.parse(source
);
155 return evaluate(doc
, returnType
);
157 catch (ParserConfigurationException e
)
159 throw new XPathExpressionException(e
);
161 catch (SAXException e
)
163 throw new XPathExpressionException(e
);
165 catch (IOException e
)
167 throw new XPathExpressionException(e
);
171 public String
evaluate(InputSource source
)
172 throws XPathExpressionException
174 return (String
) evaluate(source
, XPathConstants
.STRING
);
177 public abstract Object
evaluate(Node context
, int pos
, int len
);
179 public abstract Expr
clone(Object context
);
181 /* -- 4.1 Node Set Functions -- */
184 * The id function selects elements by their unique ID.
185 * When the argument to id is of type node-set, then the result is
186 * the union of the result of applying id to the string-value of each of
187 * the nodes in the argument node-set. When the argument to id is of any
188 * other type, the argument is converted to a string as if by a call to
189 * the string function; the string is split into a whitespace-separated
190 * list of tokens (whitespace is any sequence of characters matching the
191 * production S); the result is a node-set containing the elements in the
192 * same document as the context node that have a unique ID equal to any of
193 * the tokens in the list.
195 public static Collection
_id(Node context
, Object object
)
197 Set ret
= new HashSet();
198 if (object
instanceof Collection
)
200 Collection nodeSet
= (Collection
) object
;
201 for (Iterator i
= nodeSet
.iterator(); i
.hasNext(); )
203 String string
= stringValue((Node
) i
.next());
204 ret
.addAll(_id (context
, string
));
209 Document doc
= (context
instanceof Document
) ?
(Document
) context
:
210 context
.getOwnerDocument();
211 String string
= _string(context
, object
);
212 StringTokenizer st
= new StringTokenizer(string
, " \t\r\n");
213 while (st
.hasMoreTokens())
215 Node element
= doc
.getElementById(st
.nextToken());
226 * The local-name function returns the local part of the expanded-name of
227 * the node in the argument node-set that is first in document order. If
228 * the argument node-set is empty or the first node has no expanded-name,
229 * an empty string is returned. If the argument is omitted, it defaults to
230 * a node-set with the context node as its only member.
232 public static String
_local_name(Node context
, Collection nodeSet
)
234 Node node
= (nodeSet
== null || nodeSet
.size() == 0) ? context
:
236 return node
.getLocalName();
240 * The namespace-uri function returns the namespace URI of the
241 * expanded-name of the node in the argument node-set that is first in
242 * document order. If the argument node-set is empty, the first node has
243 * no expanded-name, or the namespace URI of the expanded-name is null, an
244 * empty string is returned. If the argument is omitted, it defaults to a
245 * node-set with the context node as its only member.
247 public static String
_namespace_uri(Node context
, Collection nodeSet
)
249 Node node
= (nodeSet
== null || nodeSet
.size() == 0) ? context
:
251 return node
.getNamespaceURI();
255 * The name function returns a string containing a QName representing the
256 * expanded-name of the node in the argument node-set that is first in
257 * document order. The QName must represent the expanded-name with respect
258 * to the namespace declarations in effect on the node whose expanded-name
259 * is being represented. Typically, this will be the QName that occurred
260 * in the XML source. This need not be the case if there are namespace
261 * declarations in effect on the node that associate multiple prefixes
262 * with the same namespace. However, an implementation may include
263 * information about the original prefix in its representation of nodes;
264 * in this case, an implementation can ensure that the returned string is
265 * always the same as the QName used in the XML source. If the argument
266 * node-set is empty or the first node has no expanded-name, an empty
267 * string is returned. If the argument it omitted, it defaults to a
268 * node-set with the context node as its only member.
270 public static String
_name(Node context
, Collection nodeSet
)
272 Node node
= (nodeSet
== null || nodeSet
.size() == 0) ? context
:
274 switch (node
.getNodeType())
276 case Node
.ATTRIBUTE_NODE
:
277 case Node
.ELEMENT_NODE
:
278 case Node
.PROCESSING_INSTRUCTION_NODE
:
279 return node
.getNodeName();
286 * Returns the first node in the set in document order.
288 static Node
firstNode(Collection nodeSet
)
290 List list
= new ArrayList(nodeSet
);
291 Collections
.sort(list
, documentOrderComparator
);
292 return (Node
) list
.get(0);
295 /* -- 4.2 String Functions -- */
298 * Implementation of the XPath <code>string</code> function.
300 public static String
_string(Node context
, Object object
)
304 return stringValue(context
);
306 if (object
instanceof String
)
308 return (String
) object
;
310 if (object
instanceof Boolean
)
312 return object
.toString();
314 if (object
instanceof Double
)
316 double d
= ((Double
) object
).doubleValue();
325 else if (Double
.isInfinite(d
))
338 String ret
= decimalFormat
.format(d
);
339 if (ret
.endsWith (".0"))
341 ret
= ret
.substring(0, ret
.length() - 2);
346 if (object
instanceof Collection
)
348 Collection nodeSet
= (Collection
) object
;
349 if (nodeSet
.isEmpty())
353 Node node
= firstNode(nodeSet
);
354 return stringValue(node
);
356 throw new IllegalArgumentException(object
.toString());
359 /* -- 4.3 Boolean Functions -- */
362 * Implementation of the XPath <code>boolean</code> function.
364 public static boolean _boolean(Node context
, Object object
)
366 if (object
instanceof Boolean
)
368 return ((Boolean
) object
).booleanValue();
370 if (object
instanceof Double
)
372 return ((Double
) object
).doubleValue() != 0.0;
374 if (object
instanceof String
)
376 return ((String
) object
).length() != 0;
378 if (object
instanceof Collection
)
380 return ((Collection
) object
).size() != 0;
382 return false; // TODO user defined types
385 /* -- 4.4 Number Functions -- */
388 * Implementation of the XPath <code>number</code> function.
390 public static double _number(Node context
, Object object
)
394 object
= Collections
.singleton(context
);
396 if (object
instanceof Double
)
398 return ((Double
) object
).doubleValue();
400 if (object
instanceof Boolean
)
402 return ((Boolean
) object
).booleanValue() ?
1.0 : 0.0;
404 if (object
instanceof Collection
)
406 // Convert node-set to string
407 object
= stringValue((Collection
) object
);
409 if (object
instanceof String
)
411 String string
= ((String
) object
).trim();
414 return Double
.parseDouble(string
);
416 catch (NumberFormatException e
)
421 return Double
.NaN
; // TODO user-defined types
425 * Computes the XPath string-value of the specified node-set.
427 public static String
stringValue(Collection nodeSet
)
429 StringBuffer buf
= new StringBuffer();
430 for (Iterator i
= nodeSet
.iterator(); i
.hasNext(); )
432 buf
.append(stringValue((Node
) i
.next()));
434 return buf
.toString();
438 * Computes the XPath string-value of the specified node.
440 public static String
stringValue(Node node
)
442 return stringValue(node
, false);
445 static String
stringValue(Node node
, boolean elementMode
)
447 switch (node
.getNodeType())
449 case Node
.DOCUMENT_NODE
: // 5.1 Root Node
450 case Node
.DOCUMENT_FRAGMENT_NODE
:
451 case Node
.ELEMENT_NODE
: // 5.2 Element Nodes
452 StringBuffer buf
= new StringBuffer();
453 for (Node ctx
= node
.getFirstChild(); ctx
!= null;
454 ctx
= ctx
.getNextSibling())
456 buf
.append(stringValue(ctx
, true));
458 return buf
.toString();
459 case Node
.TEXT_NODE
: // 5.7 Text Nodes
460 case Node
.CDATA_SECTION_NODE
:
461 return node
.getNodeValue();
462 case Node
.ATTRIBUTE_NODE
: // 5.3 Attribute Nodes
463 case Node
.PROCESSING_INSTRUCTION_NODE
: // 5.5 Processing Instruction
464 case Node
.COMMENT_NODE
: // 5.6 Comment Nodes
467 return node
.getNodeValue();