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 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: }