OSDN Git Service

Imported GNU Classpath 0.20
[pf3gnuchains/gcc-fork.git] / libjava / classpath / gnu / xml / xpath / Expr.java
1 /* Expr.java -- 
2    Copyright (C) 2004,2006 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
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)
9 any later version.
10
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.
15
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., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
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
24 combination.
25
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. */
37
38 package gnu.xml.xpath;
39
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;
51 import java.util.Set;
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;
64
65 /**
66  * An XPath expression.
67  * This can be evaluated in the context of a node to produce a result.
68  *
69  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
70  */
71 public abstract class Expr
72   implements XPathExpression
73 {
74
75   protected static final Comparator documentOrderComparator =
76     new DocumentOrderComparator();
77
78   protected static final DecimalFormat decimalFormat =
79     new DecimalFormat("####################################################" +
80                       ".####################################################",
81                       new DecimalFormatSymbols(Locale.US));
82
83   public Object evaluate(Object item, QName returnType)
84     throws XPathExpressionException
85   {
86     Object ret = null;
87     Node context = null;
88     if (item instanceof Node)
89       {
90         context = (Node) item;
91         ret = evaluate(context, 1, 1);
92         if (XPathConstants.STRING == returnType &&
93             !(ret instanceof String))
94           {
95             ret = _string(context, ret);
96           }
97         else if (XPathConstants.NUMBER == returnType &&
98                  !(ret instanceof Double))
99           {
100             ret = new Double(_number(context, ret));
101           }
102         else if (XPathConstants.BOOLEAN == returnType &&
103                  !(ret instanceof Boolean))
104           {
105             ret = _boolean(context, ret) ? Boolean.TRUE : Boolean.FALSE;
106           }
107         else if (XPathConstants.NODE == returnType)
108           {
109             if (ret instanceof Collection)
110               {
111                 Collection ns = (Collection) ret;
112                 switch (ns.size())
113                   {
114                   case 0:
115                     ret = null;
116                     break;
117                   case 1:
118                     ret = (Node) ns.iterator().next();
119                     break;
120                   default:
121                     throw new XPathExpressionException("multiple nodes in node-set");
122                   }
123               }
124             else if (ret != null)
125               {
126                 throw new XPathExpressionException("return value is not a node-set");
127               }
128           }
129         else if (XPathConstants.NODESET == returnType)
130           {
131             if (ret != null && !(ret instanceof Collection))
132               {
133                 throw new XPathExpressionException("return value is not a node-set");
134               }
135           }
136       }
137     return ret;
138   }
139
140   public String evaluate(Object item)
141     throws XPathExpressionException
142   {
143     return (String) evaluate(item, XPathConstants.STRING); 
144   }
145
146   public Object evaluate(InputSource source, QName returnType)
147     throws XPathExpressionException
148   {
149     try
150       {
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);
156       }
157     catch (ParserConfigurationException e)
158       {
159         throw new XPathExpressionException(e); 
160       }
161     catch (SAXException e)
162       {
163         throw new XPathExpressionException(e); 
164       }
165     catch (IOException e)
166       {
167         throw new XPathExpressionException(e); 
168       }
169   }
170
171   public String evaluate(InputSource source)
172     throws XPathExpressionException
173   {
174     return (String) evaluate(source, XPathConstants.STRING);
175   }
176
177   public abstract Object evaluate(Node context, int pos, int len);
178
179   public abstract Expr clone(Object context);
180
181   public abstract boolean references(QName var);
182   
183   /* -- 4.1 Node Set Functions -- */
184
185   /**
186    * The id function selects elements by their unique ID.
187    * When the argument to id is of type node-set, then the result is
188    * the union of the result of applying id to the string-value of each of
189    * the nodes in the argument node-set. When the argument to id is of any
190    * other type, the argument is converted to a string as if by a call to
191    * the string function; the string is split into a whitespace-separated
192    * list of tokens (whitespace is any sequence of characters matching the
193    * production S); the result is a node-set containing the elements in the
194    * same document as the context node that have a unique ID equal to any of
195    * the tokens in the list.
196    */
197   public static Collection _id(Node context, Object object)
198   {
199     Set ret = new HashSet();
200     if (object instanceof Collection)
201       {
202         Collection nodeSet = (Collection) object;
203         for (Iterator i = nodeSet.iterator(); i.hasNext(); )
204           {
205             String string = stringValue((Node) i.next());
206             ret.addAll(_id (context, string));
207           }
208       }
209     else
210       {
211         Document doc = (context instanceof Document) ? (Document) context :
212           context.getOwnerDocument();
213         String string = _string(context, object);
214         StringTokenizer st = new StringTokenizer(string, " \t\r\n");
215         while (st.hasMoreTokens())
216           {
217             Node element = doc.getElementById(st.nextToken());
218             if (element != null)
219               {
220                 ret.add(element);
221               }
222           }
223       }
224     return ret;
225   }
226
227   /**
228    * The local-name function returns the local part of the expanded-name of
229    * the node in the argument node-set that is first in document order. If
230    * the argument node-set is empty or the first node has no expanded-name,
231    * an empty string is returned. If the argument is omitted, it defaults to
232    * a node-set with the context node as its only member.
233    */
234   public static String _local_name(Node context, Collection nodeSet)
235   {
236     if (nodeSet == null || nodeSet.isEmpty())
237       return "";
238     Node node = firstNode(nodeSet);
239     String ret = node.getLocalName();
240     return (ret == null) ? "" : ret;
241   }
242
243   /**
244    * The namespace-uri function returns the namespace URI of the
245    * expanded-name of the node in the argument node-set that is first in
246    * document order. If the argument node-set is empty, the first node has
247    * no expanded-name, or the namespace URI of the expanded-name is null, an
248    * empty string is returned. If the argument is omitted, it defaults to a
249    * node-set with the context node as its only member.
250    */
251   public static String _namespace_uri(Node context, Collection nodeSet)
252   {
253     if (nodeSet == null || nodeSet.isEmpty())
254       return "";
255     Node node = firstNode(nodeSet);
256     String ret = node.getNamespaceURI();
257     return (ret == null) ? "" : ret;
258   }
259   
260   /**
261    * The name function returns a string containing a QName representing the
262    * expanded-name of the node in the argument node-set that is first in
263    * document order. The QName must represent the expanded-name with respect
264    * to the namespace declarations in effect on the node whose expanded-name
265    * is being represented. Typically, this will be the QName that occurred
266    * in the XML source. This need not be the case if there are namespace
267    * declarations in effect on the node that associate multiple prefixes
268    * with the same namespace. However, an implementation may include
269    * information about the original prefix in its representation of nodes;
270    * in this case, an implementation can ensure that the returned string is
271    * always the same as the QName used in the XML source. If the argument
272    * node-set is empty or the first node has no expanded-name, an empty
273    * string is returned. If the argument it omitted, it defaults to a
274    * node-set with the context node as its only member.
275    */
276   public static String _name(Node context, Collection nodeSet)
277   {
278     if (nodeSet == null || nodeSet.isEmpty())
279       return "";
280     Node node = firstNode(nodeSet);
281     String ret = null;
282     switch (node.getNodeType())
283       {
284       case Node.ATTRIBUTE_NODE:
285       case Node.ELEMENT_NODE:
286       case Node.PROCESSING_INSTRUCTION_NODE:
287         ret = node.getNodeName();
288       }
289     return (ret == null) ? "" : ret;
290   }
291
292   /**
293    * Returns the first node in the set in document order.
294    */
295   static Node firstNode(Collection nodeSet)
296   {
297     List list = new ArrayList(nodeSet);
298     Collections.sort(list, documentOrderComparator);
299     return (Node) list.get(0);
300   }
301
302   /* -- 4.2 String Functions -- */
303
304   /**
305    * Implementation of the XPath <code>string</code> function.
306    */
307   public static String _string(Node context, Object object)
308   {
309     if (object == null)
310       {
311         return stringValue(context);
312       }
313     if (object instanceof String)
314       {
315         return (String) object;
316       }
317     if (object instanceof Boolean)
318       {
319         return object.toString();
320       }
321     if (object instanceof Double)
322       {
323         double d = ((Double) object).doubleValue();
324         if (Double.isNaN(d))
325           {
326             return "NaN";
327           }
328         else if (d == 0.0d)
329           {
330             return "0";
331           }
332         else if (Double.isInfinite(d))
333           {
334             if (d < 0)
335               {
336                 return "-Infinity";
337               }
338             else
339               {
340                 return "Infinity";
341               }
342           }
343         else
344           {
345             String ret = decimalFormat.format(d);
346             if (ret.endsWith (".0"))
347               { 
348                 ret = ret.substring(0, ret.length() - 2);
349               }
350             return ret;
351           }
352       }
353     if (object instanceof Collection)
354       {
355         Collection nodeSet = (Collection) object;
356         if (nodeSet.isEmpty())
357           {
358             return "";
359           }
360         Node node = firstNode(nodeSet);
361         return stringValue(node);
362       }
363     throw new IllegalArgumentException(object.toString());
364   }
365
366   /* -- 4.3 Boolean Functions -- */
367   
368   /**
369    * Implementation of the XPath <code>boolean</code> function.
370    */
371   public static boolean _boolean(Node context, Object object)
372   {
373     if (object instanceof Boolean)
374       {
375         return ((Boolean) object).booleanValue();
376       }
377     if (object instanceof Double)
378       {
379         Double value = (Double) object;
380         if (value.isNaN())
381           return false;
382         return value.doubleValue() != 0.0;
383       }
384     if (object instanceof String)
385       {
386         return ((String) object).length() != 0;
387       }
388     if (object instanceof Collection)
389       {
390         return ((Collection) object).size() != 0;
391       }
392     return false; // TODO user defined types
393   }
394
395   /* -- 4.4 Number Functions -- */
396
397   /**
398    * Implementation of the XPath <code>number</code> function.
399    */
400   public static double _number(Node context, Object object)
401   {
402     if (object == null)
403       {
404         object = Collections.singleton(context);
405       }
406     if (object instanceof Double)
407       {
408         return ((Double) object).doubleValue();
409       }
410     if (object instanceof Boolean)
411       {
412         return ((Boolean) object).booleanValue() ? 1.0 : 0.0;
413       }
414     if (object instanceof Collection)
415       {
416         // Convert node-set to string
417         object = stringValue((Collection) object);
418       }
419     if (object instanceof String)
420       {
421         String string = ((String) object).trim();
422         try
423           {
424             return Double.parseDouble(string);
425           }
426         catch (NumberFormatException e)
427           {
428             return Double.NaN;
429           }
430       }
431     return Double.NaN; // TODO user-defined types
432   }
433
434   /**
435    * Computes the XPath string-value of the specified node-set.
436    */
437   public static String stringValue(Collection nodeSet)
438   {
439     StringBuffer buf = new StringBuffer();
440     for (Iterator i = nodeSet.iterator(); i.hasNext(); )
441       {
442         buf.append(stringValue((Node) i.next()));
443       }
444     return buf.toString();
445   }
446
447   /**
448    * Computes the XPath string-value of the specified node.
449    */
450   public static String stringValue(Node node)
451   {
452     return stringValue(node, false);
453   }
454   
455   static String stringValue(Node node, boolean elementMode)
456   {
457     switch (node.getNodeType())
458       {
459       case Node.DOCUMENT_NODE: // 5.1 Root Node
460       case Node.DOCUMENT_FRAGMENT_NODE:
461       case Node.ELEMENT_NODE: // 5.2 Element Nodes
462         StringBuffer buf = new StringBuffer();
463         for (Node ctx = node.getFirstChild(); ctx != null;
464              ctx = ctx.getNextSibling())
465           {
466             buf.append(stringValue(ctx, true));
467           }
468         return buf.toString();
469       case Node.TEXT_NODE: // 5.7 Text Nodes
470       case Node.CDATA_SECTION_NODE:
471         return node.getNodeValue();
472       case Node.ATTRIBUTE_NODE: // 5.3 Attribute Nodes
473       case Node.PROCESSING_INSTRUCTION_NODE: // 5.5 Processing Instruction
474       case Node.COMMENT_NODE: // 5.6 Comment Nodes
475         if (!elementMode)
476           {
477             return node.getNodeValue();
478           }
479       default:
480         return "";
481       }
482   }
483
484   static int intValue(Object val)
485   {
486     if (val instanceof Double)
487       {
488         Double d = (Double) val;
489         return d.isNaN() ? 0 : d.intValue();
490       }
491     else
492       return (int) Math.ceil(_number(null, val));
493   }
494
495 }