Source for gnu.xml.xpath.Expr

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