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