mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			550 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			550 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Java
		
	
	
	
| /* Expr.java --
 | |
|    Copyright (C) 2004,2006 Free Software Foundation, Inc.
 | |
| 
 | |
| This file is part of GNU Classpath.
 | |
| 
 | |
| GNU Classpath is free software; you can redistribute it and/or modify
 | |
| it under the terms of the GNU General Public License as published by
 | |
| the Free Software Foundation; either version 2, or (at your option)
 | |
| any later version.
 | |
| 
 | |
| GNU Classpath is distributed in the hope that it will be useful, but
 | |
| WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
| General Public License for more details.
 | |
| 
 | |
| You should have received a copy of the GNU General Public License
 | |
| along with GNU Classpath; see the file COPYING.  If not, write to the
 | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | |
| 02110-1301 USA.
 | |
| 
 | |
| Linking this library statically or dynamically with other modules is
 | |
| making a combined work based on this library.  Thus, the terms and
 | |
| conditions of the GNU General Public License cover the whole
 | |
| combination.
 | |
| 
 | |
| As a special exception, the copyright holders of this library give you
 | |
| permission to link this library with independent modules to produce an
 | |
| executable, regardless of the license terms of these independent
 | |
| modules, and to copy and distribute the resulting executable under
 | |
| terms of your choice, provided that you also meet, for each linked
 | |
| independent module, the terms and conditions of the license of that
 | |
| module.  An independent module is a module which is not derived from
 | |
| or based on this library.  If you modify this library, you may extend
 | |
| this exception to your version of the library, but you are not
 | |
| obligated to do so.  If you do not wish to do so, delete this
 | |
| exception statement from your version. */
 | |
| 
 | |
| package gnu.xml.xpath;
 | |
| 
 | |
| import gnu.java.lang.CPStringBuilder;
 | |
| 
 | |
| import java.io.IOException;
 | |
| import java.text.DecimalFormat;
 | |
| import java.text.DecimalFormatSymbols;
 | |
| import java.util.ArrayList;
 | |
| import java.util.Collection;
 | |
| import java.util.Collections;
 | |
| import java.util.Comparator;
 | |
| import java.util.HashSet;
 | |
| import java.util.Iterator;
 | |
| import java.util.List;
 | |
| import java.util.Locale;
 | |
| import java.util.Set;
 | |
| import java.util.StringTokenizer;
 | |
| import javax.xml.namespace.QName;
 | |
| import javax.xml.parsers.DocumentBuilder;
 | |
| import javax.xml.parsers.DocumentBuilderFactory;
 | |
| import javax.xml.parsers.ParserConfigurationException;
 | |
| import javax.xml.xpath.XPathConstants;
 | |
| import javax.xml.xpath.XPathExpression;
 | |
| import javax.xml.xpath.XPathExpressionException;
 | |
| import org.w3c.dom.Document;
 | |
| import org.w3c.dom.Node;
 | |
| import org.w3c.dom.NodeList;
 | |
| import org.xml.sax.InputSource;
 | |
| import org.xml.sax.SAXException;
 | |
| 
 | |
| /**
 | |
|  * An XPath expression.
 | |
|  * This can be evaluated in the context of a node to produce a result.
 | |
|  *
 | |
|  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
 | |
|  */
 | |
| public abstract class Expr
 | |
|   implements XPathExpression
 | |
| {
 | |
| 
 | |
|   protected static final Comparator<Node> documentOrderComparator =
 | |
|     new DocumentOrderComparator();
 | |
| 
 | |
|   protected static final DecimalFormat decimalFormat =
 | |
|     new DecimalFormat("####################################################" +
 | |
|                       ".####################################################",
 | |
|                       new DecimalFormatSymbols(Locale.US));
 | |
| 
 | |
|   static class ExprNodeSet implements NodeList
 | |
|   {
 | |
| 
 | |
|     private ArrayList<Node> list;
 | |
| 
 | |
|     ExprNodeSet(Collection<Node> collection)
 | |
|     {
 | |
|       if (collection instanceof ArrayList)
 | |
|         list = (ArrayList<Node>) collection;
 | |
|       else
 | |
|         list = new ArrayList<Node>(collection);
 | |
|     }
 | |
| 
 | |
|     public int getLength()
 | |
|     {
 | |
|       return list.size();
 | |
|     }
 | |
| 
 | |
|     public Node item(int index)
 | |
|     {
 | |
|       try
 | |
|         {
 | |
|           return list.get(index);
 | |
|         }
 | |
|       catch (ArrayIndexOutOfBoundsException e)
 | |
|         {
 | |
|           return null;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   }
 | |
| 
 | |
|   public Object evaluate(Object item, QName returnType)
 | |
|     throws XPathExpressionException
 | |
|   {
 | |
|     Object ret = null;
 | |
|     Node context = null;
 | |
|     if (item instanceof Node)
 | |
|       {
 | |
|         context = (Node) item;
 | |
|         ret = evaluate(context, 1, 1);
 | |
|         if (XPathConstants.STRING == returnType &&
 | |
|             !(ret instanceof String))
 | |
|           {
 | |
|             ret = _string(context, ret);
 | |
|           }
 | |
|         else if (XPathConstants.NUMBER == returnType &&
 | |
|                  !(ret instanceof Double))
 | |
|           {
 | |
|             ret = new Double(_number(context, ret));
 | |
|           }
 | |
|         else if (XPathConstants.BOOLEAN == returnType &&
 | |
|                  !(ret instanceof Boolean))
 | |
|           {
 | |
|             ret = _boolean(context, ret) ? Boolean.TRUE : Boolean.FALSE;
 | |
|           }
 | |
|         else if (XPathConstants.NODE == returnType)
 | |
|           {
 | |
|             if (ret instanceof Collection)
 | |
|               {
 | |
|                 /* Suppression is safe, as we know context
 | |
|                    produces Collection<Node> */
 | |
|                 @SuppressWarnings("unchecked")
 | |
|                   Collection<Node> ns = (Collection<Node>) ret;
 | |
|                 switch (ns.size())
 | |
|                   {
 | |
|                   case 0:
 | |
|                     ret = null;
 | |
|                     break;
 | |
|                   case 1:
 | |
|                     ret = ns.iterator().next();
 | |
|                     break;
 | |
|                   default:
 | |
|                     throw new XPathExpressionException("multiple nodes in node-set");
 | |
|                   }
 | |
|               }
 | |
|             else if (ret != null)
 | |
|               {
 | |
|                 throw new XPathExpressionException("return value is not a node-set");
 | |
|               }
 | |
|           }
 | |
|         else if (XPathConstants.NODESET == returnType)
 | |
|           {
 | |
|             if (ret != null && !(ret instanceof Collection))
 | |
|               {
 | |
|                 throw new XPathExpressionException("return value is not a node-set");
 | |
|               }
 | |
|             if (ret != null)
 | |
|               {
 | |
|                 /* Suppression is safe, as we know context produces Collection<Node> */
 | |
|                 @SuppressWarnings("unchecked")
 | |
|                   Collection<Node> nodes = (Collection<Node>) ret;
 | |
|                 ret = new ExprNodeSet(nodes);
 | |
|               }
 | |
|           }
 | |
|       }
 | |
|     return ret;
 | |
|   }
 | |
| 
 | |
|   public String evaluate(Object item)
 | |
|     throws XPathExpressionException
 | |
|   {
 | |
|     return (String) evaluate(item, XPathConstants.STRING);
 | |
|   }
 | |
| 
 | |
|   public Object evaluate(InputSource source, QName returnType)
 | |
|     throws XPathExpressionException
 | |
|   {
 | |
|     try
 | |
|       {
 | |
|         DocumentBuilderFactory factory =
 | |
|           new gnu.xml.dom.JAXPFactory();
 | |
|         DocumentBuilder builder = factory.newDocumentBuilder();
 | |
|         Document doc = builder.parse(source);
 | |
|         return evaluate(doc, returnType);
 | |
|       }
 | |
|     catch (ParserConfigurationException e)
 | |
|       {
 | |
|         throw new XPathExpressionException(e);
 | |
|       }
 | |
|     catch (SAXException e)
 | |
|       {
 | |
|         throw new XPathExpressionException(e);
 | |
|       }
 | |
|     catch (IOException e)
 | |
|       {
 | |
|         throw new XPathExpressionException(e);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   public String evaluate(InputSource source)
 | |
|     throws XPathExpressionException
 | |
|   {
 | |
|     return (String) evaluate(source, XPathConstants.STRING);
 | |
|   }
 | |
| 
 | |
|   public abstract Object evaluate(Node context, int pos, int len);
 | |
| 
 | |
|   public abstract Expr clone(Object context);
 | |
| 
 | |
|   public abstract boolean references(QName var);
 | |
| 
 | |
|   /* -- 4.1 Node Set Functions -- */
 | |
| 
 | |
|   /**
 | |
|    * The id function selects elements by their unique ID.
 | |
|    * When the argument to id is of type node-set, then the result is
 | |
|    * the union of the result of applying id to the string-value of each of
 | |
|    * the nodes in the argument node-set. When the argument to id is of any
 | |
|    * other type, the argument is converted to a string as if by a call to
 | |
|    * the string function; the string is split into a whitespace-separated
 | |
|    * list of tokens (whitespace is any sequence of characters matching the
 | |
|    * production S); the result is a node-set containing the elements in the
 | |
|    * same document as the context node that have a unique ID equal to any of
 | |
|    * the tokens in the list.
 | |
|    */
 | |
|   public static Collection<Node> _id(Node context, Object object)
 | |
|   {
 | |
|     Set<Node> ret = new HashSet<Node>();
 | |
|     if (object instanceof Collection)
 | |
|       {
 | |
|         /* Suppression is safe, as the iteration will check each value is a Node */
 | |
|         @SuppressWarnings("unchecked")
 | |
|           Collection<Node> nodeSet = (Collection<Node>) object;
 | |
|         for (Iterator<Node> i = nodeSet.iterator(); i.hasNext(); )
 | |
|           {
 | |
|             String string = stringValue(i.next());
 | |
|             ret.addAll(_id (context, string));
 | |
|           }
 | |
|       }
 | |
|     else
 | |
|       {
 | |
|         Document doc = (context instanceof Document) ? (Document) context :
 | |
|           context.getOwnerDocument();
 | |
|         String string = _string(context, object);
 | |
|         StringTokenizer st = new StringTokenizer(string, " \t\r\n");
 | |
|         while (st.hasMoreTokens())
 | |
|           {
 | |
|             Node element = doc.getElementById(st.nextToken());
 | |
|             if (element != null)
 | |
|               {
 | |
|                 ret.add(element);
 | |
|               }
 | |
|           }
 | |
|       }
 | |
|     return ret;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * The local-name function returns the local part of the expanded-name of
 | |
|    * the node in the argument node-set that is first in document order. If
 | |
|    * the argument node-set is empty or the first node has no expanded-name,
 | |
|    * an empty string is returned. If the argument is omitted, it defaults to
 | |
|    * a node-set with the context node as its only member.
 | |
|    */
 | |
|   public static String _local_name(Node context, Collection<Node> nodeSet)
 | |
|   {
 | |
|     if (nodeSet == null || nodeSet.isEmpty())
 | |
|       return "";
 | |
|     Node node = firstNode(nodeSet);
 | |
|     String ret = node.getLocalName();
 | |
|     return (ret == null) ? "" : ret;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * The namespace-uri function returns the namespace URI of the
 | |
|    * expanded-name of the node in the argument node-set that is first in
 | |
|    * document order. If the argument node-set is empty, the first node has
 | |
|    * no expanded-name, or the namespace URI of the expanded-name is null, an
 | |
|    * empty string is returned. If the argument is omitted, it defaults to a
 | |
|    * node-set with the context node as its only member.
 | |
|    */
 | |
|   public static String _namespace_uri(Node context, Collection<Node> nodeSet)
 | |
|   {
 | |
|     if (nodeSet == null || nodeSet.isEmpty())
 | |
|       return "";
 | |
|     Node node = firstNode(nodeSet);
 | |
|     String ret = node.getNamespaceURI();
 | |
|     return (ret == null) ? "" : ret;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * The name function returns a string containing a QName representing the
 | |
|    * expanded-name of the node in the argument node-set that is first in
 | |
|    * document order. The QName must represent the expanded-name with respect
 | |
|    * to the namespace declarations in effect on the node whose expanded-name
 | |
|    * is being represented. Typically, this will be the QName that occurred
 | |
|    * in the XML source. This need not be the case if there are namespace
 | |
|    * declarations in effect on the node that associate multiple prefixes
 | |
|    * with the same namespace. However, an implementation may include
 | |
|    * information about the original prefix in its representation of nodes;
 | |
|    * in this case, an implementation can ensure that the returned string is
 | |
|    * always the same as the QName used in the XML source. If the argument
 | |
|    * node-set is empty or the first node has no expanded-name, an empty
 | |
|    * string is returned. If the argument it omitted, it defaults to a
 | |
|    * node-set with the context node as its only member.
 | |
|    */
 | |
|   public static String _name(Node context, Collection<Node> nodeSet)
 | |
|   {
 | |
|     if (nodeSet == null || nodeSet.isEmpty())
 | |
|       return "";
 | |
|     Node node = firstNode(nodeSet);
 | |
|     String ret = null;
 | |
|     switch (node.getNodeType())
 | |
|       {
 | |
|       case Node.ATTRIBUTE_NODE:
 | |
|       case Node.ELEMENT_NODE:
 | |
|       case Node.PROCESSING_INSTRUCTION_NODE:
 | |
|         ret = node.getNodeName();
 | |
|       }
 | |
|     return (ret == null) ? "" : ret;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the first node in the set in document order.
 | |
|    */
 | |
|   static Node firstNode(Collection<Node> nodeSet)
 | |
|   {
 | |
|     List<Node> list = new ArrayList<Node>(nodeSet);
 | |
|     Collections.sort(list, documentOrderComparator);
 | |
|     return list.get(0);
 | |
|   }
 | |
| 
 | |
|   /* -- 4.2 String Functions -- */
 | |
| 
 | |
|   /**
 | |
|    * Implementation of the XPath <code>string</code> function.
 | |
|    */
 | |
|   public static String _string(Node context, Object object)
 | |
|   {
 | |
|     if (object == null)
 | |
|       {
 | |
|         return stringValue(context);
 | |
|       }
 | |
|     if (object instanceof String)
 | |
|       {
 | |
|         return (String) object;
 | |
|       }
 | |
|     if (object instanceof Boolean)
 | |
|       {
 | |
|         return object.toString();
 | |
|       }
 | |
|     if (object instanceof Double)
 | |
|       {
 | |
|         double d = ((Double) object).doubleValue();
 | |
|         if (Double.isNaN(d))
 | |
|           {
 | |
|             return "NaN";
 | |
|           }
 | |
|         else if (d == 0.0d)
 | |
|           {
 | |
|             return "0";
 | |
|           }
 | |
|         else if (Double.isInfinite(d))
 | |
|           {
 | |
|             if (d < 0)
 | |
|               {
 | |
|                 return "-Infinity";
 | |
|               }
 | |
|             else
 | |
|               {
 | |
|                 return "Infinity";
 | |
|               }
 | |
|           }
 | |
|         else
 | |
|           {
 | |
|             String ret = decimalFormat.format(d);
 | |
|             if (ret.endsWith (".0"))
 | |
|               {
 | |
|                 ret = ret.substring(0, ret.length() - 2);
 | |
|               }
 | |
|             return ret;
 | |
|           }
 | |
|       }
 | |
|     if (object instanceof Collection)
 | |
|       {
 | |
|         /* Suppression is safe, as we fail immediately if the
 | |
|          * first element is not a Node and don't use the rest */
 | |
|         @SuppressWarnings("unchecked")
 | |
|           Collection<Node> nodeSet = (Collection<Node>) object;
 | |
|         if (nodeSet.isEmpty())
 | |
|           {
 | |
|             return "";
 | |
|           }
 | |
|         Node node = firstNode(nodeSet);
 | |
|         return stringValue(node);
 | |
|       }
 | |
|     throw new IllegalArgumentException(object.toString());
 | |
|   }
 | |
| 
 | |
|   /* -- 4.3 Boolean Functions -- */
 | |
| 
 | |
|   /**
 | |
|    * Implementation of the XPath <code>boolean</code> function.
 | |
|    */
 | |
|   public static boolean _boolean(Node context, Object object)
 | |
|   {
 | |
|     if (object instanceof Boolean)
 | |
|       {
 | |
|         return ((Boolean) object).booleanValue();
 | |
|       }
 | |
|     if (object instanceof Double)
 | |
|       {
 | |
|         Double value = (Double) object;
 | |
|         if (value.isNaN())
 | |
|           return false;
 | |
|         return value.doubleValue() != 0.0;
 | |
|       }
 | |
|     if (object instanceof String)
 | |
|       {
 | |
|         return ((String) object).length() != 0;
 | |
|       }
 | |
|     if (object instanceof Collection)
 | |
|       {
 | |
|         return ((Collection<?>) object).size() != 0;
 | |
|       }
 | |
|     return false; // TODO user defined types
 | |
|   }
 | |
| 
 | |
|   /* -- 4.4 Number Functions -- */
 | |
| 
 | |
|   /**
 | |
|    * Implementation of the XPath <code>number</code> function.
 | |
|    */
 | |
|   public static double _number(Node context, Object object)
 | |
|   {
 | |
|     if (object == null)
 | |
|       {
 | |
|         object = Collections.singleton(context);
 | |
|       }
 | |
|     if (object instanceof Double)
 | |
|       {
 | |
|         return ((Double) object).doubleValue();
 | |
|       }
 | |
|     if (object instanceof Boolean)
 | |
|       {
 | |
|         return ((Boolean) object).booleanValue() ? 1.0 : 0.0;
 | |
|       }
 | |
|     if (object instanceof Collection)
 | |
|       {
 | |
|         /* Suppression is safe, as we fail immediately if one
 | |
|          * of the elements is not a Node */
 | |
|         @SuppressWarnings("unchecked")
 | |
|           Collection<Node> nodeSet = (Collection<Node>) object;
 | |
|         // Convert node-set to string
 | |
|         object = stringValue(nodeSet);
 | |
|       }
 | |
|     if (object instanceof String)
 | |
|       {
 | |
|         String string = ((String) object).trim();
 | |
|         try
 | |
|           {
 | |
|             return Double.parseDouble(string);
 | |
|           }
 | |
|         catch (NumberFormatException e)
 | |
|           {
 | |
|             return Double.NaN;
 | |
|           }
 | |
|       }
 | |
|     return Double.NaN; // TODO user-defined types
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Computes the XPath string-value of the specified node-set.
 | |
|    */
 | |
|   public static String stringValue(Collection<Node> nodeSet)
 | |
|   {
 | |
|     CPStringBuilder buf = new CPStringBuilder();
 | |
|     for (Iterator<Node> i = nodeSet.iterator(); i.hasNext(); )
 | |
|       {
 | |
|         buf.append(stringValue(i.next()));
 | |
|       }
 | |
|     return buf.toString();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Computes the XPath string-value of the specified node.
 | |
|    */
 | |
|   public static String stringValue(Node node)
 | |
|   {
 | |
|     return stringValue(node, false);
 | |
|   }
 | |
| 
 | |
|   static String stringValue(Node node, boolean elementMode)
 | |
|   {
 | |
|     switch (node.getNodeType())
 | |
|       {
 | |
|       case Node.DOCUMENT_NODE: // 5.1 Root Node
 | |
|       case Node.DOCUMENT_FRAGMENT_NODE:
 | |
|       case Node.ELEMENT_NODE: // 5.2 Element Nodes
 | |
|         CPStringBuilder buf = new CPStringBuilder();
 | |
|         for (Node ctx = node.getFirstChild(); ctx != null;
 | |
|              ctx = ctx.getNextSibling())
 | |
|           {
 | |
|             buf.append(stringValue(ctx, true));
 | |
|           }
 | |
|         return buf.toString();
 | |
|       case Node.TEXT_NODE: // 5.7 Text Nodes
 | |
|       case Node.CDATA_SECTION_NODE:
 | |
|         return node.getNodeValue();
 | |
|       case Node.ATTRIBUTE_NODE: // 5.3 Attribute Nodes
 | |
|       case Node.PROCESSING_INSTRUCTION_NODE: // 5.5 Processing Instruction
 | |
|       case Node.COMMENT_NODE: // 5.6 Comment Nodes
 | |
|         if (!elementMode)
 | |
|           {
 | |
|             return node.getNodeValue();
 | |
|           }
 | |
|       default:
 | |
|         return "";
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   static int intValue(Object val)
 | |
|   {
 | |
|     if (val instanceof Double)
 | |
|       {
 | |
|         Double d = (Double) val;
 | |
|         return d.isNaN() ? 0 : d.intValue();
 | |
|       }
 | |
|     else
 | |
|       return (int) Math.ceil(_number(null, val));
 | |
|   }
 | |
| 
 | |
| }
 |