Source for gnu.xml.xpath.Expr

   1: /* Expr.java -- 
   2:    Copyright (C) 2004 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:     Node node = (nodeSet == null || nodeSet.size() == 0) ? context :
 237:       firstNode(nodeSet);
 238:     return node.getLocalName();
 239:   }
 240: 
 241:   /**
 242:    * The namespace-uri function returns the namespace URI of the
 243:    * expanded-name of the node in the argument node-set that is first in
 244:    * document order. If the argument node-set is empty, the first node has
 245:    * no expanded-name, or the namespace URI of the expanded-name is null, an
 246:    * empty string is returned. If the argument is omitted, it defaults to a
 247:    * node-set with the context node as its only member.
 248:    */
 249:   public static String _namespace_uri(Node context, Collection nodeSet)
 250:   {
 251:     Node node = (nodeSet == null || nodeSet.size() == 0) ? context :
 252:       firstNode(nodeSet);
 253:     return node.getNamespaceURI();
 254:   }
 255:   
 256:   /**
 257:    * The name function returns a string containing a QName representing the
 258:    * expanded-name of the node in the argument node-set that is first in
 259:    * document order. The QName must represent the expanded-name with respect
 260:    * to the namespace declarations in effect on the node whose expanded-name
 261:    * is being represented. Typically, this will be the QName that occurred
 262:    * in the XML source. This need not be the case if there are namespace
 263:    * declarations in effect on the node that associate multiple prefixes
 264:    * with the same namespace. However, an implementation may include
 265:    * information about the original prefix in its representation of nodes;
 266:    * in this case, an implementation can ensure that the returned string is
 267:    * always the same as the QName used in the XML source. If the argument
 268:    * node-set is empty or the first node has no expanded-name, an empty
 269:    * string is returned. If the argument it omitted, it defaults to a
 270:    * node-set with the context node as its only member.
 271:    */
 272:   public static String _name(Node context, Collection nodeSet)
 273:   {
 274:     Node node = (nodeSet == null || nodeSet.size() == 0) ? context :
 275:       firstNode(nodeSet);
 276:     switch (node.getNodeType())
 277:       {
 278:       case Node.ATTRIBUTE_NODE:
 279:       case Node.ELEMENT_NODE:
 280:       case Node.PROCESSING_INSTRUCTION_NODE:
 281:         return node.getNodeName();
 282:       default:
 283:         return "";
 284:       }
 285:   }
 286: 
 287:   /**
 288:    * Returns the first node in the set in document order.
 289:    */
 290:   static Node firstNode(Collection nodeSet)
 291:   {
 292:     List list = new ArrayList(nodeSet);
 293:     Collections.sort(list, documentOrderComparator);
 294:     return (Node) list.get(0);
 295:   }
 296: 
 297:   /* -- 4.2 String Functions -- */
 298: 
 299:   /**
 300:    * Implementation of the XPath <code>string</code> function.
 301:    */
 302:   public static String _string(Node context, Object object)
 303:   {
 304:     if (object == null)
 305:       {
 306:         return stringValue(context);
 307:       }
 308:     if (object instanceof String)
 309:       {
 310:         return (String) object;
 311:       }
 312:     if (object instanceof Boolean)
 313:       {
 314:         return object.toString();
 315:       }
 316:     if (object instanceof Double)
 317:       {
 318:         double d = ((Double) object).doubleValue();
 319:         if (Double.isNaN(d))
 320:           {
 321:             return "NaN";
 322:           }
 323:         else if (d == 0.0d)
 324:           {
 325:             return "0";
 326:           }
 327:         else if (Double.isInfinite(d))
 328:           {
 329:             if (d < 0)
 330:               {
 331:                 return "-Infinity";
 332:               }
 333:             else
 334:               {
 335:                 return "Infinity";
 336:               }
 337:           }
 338:         else
 339:           {
 340:             String ret = decimalFormat.format(d);
 341:             if (ret.endsWith (".0"))
 342:               { 
 343:                 ret = ret.substring(0, ret.length() - 2);
 344:               }
 345:             return ret;
 346:           }
 347:       }
 348:     if (object instanceof Collection)
 349:       {
 350:         Collection nodeSet = (Collection) object;
 351:         if (nodeSet.isEmpty())
 352:           {
 353:             return "";
 354:           }
 355:         Node node = firstNode(nodeSet);
 356:         return stringValue(node);
 357:       }
 358:     throw new IllegalArgumentException(object.toString());
 359:   }
 360: 
 361:   /* -- 4.3 Boolean Functions -- */
 362:   
 363:   /**
 364:    * Implementation of the XPath <code>boolean</code> function.
 365:    */
 366:   public static boolean _boolean(Node context, Object object)
 367:   {
 368:     if (object instanceof Boolean)
 369:       {
 370:         return ((Boolean) object).booleanValue();
 371:       }
 372:     if (object instanceof Double)
 373:       {
 374:         return ((Double) object).doubleValue() != 0.0;
 375:       }
 376:     if (object instanceof String)
 377:       {
 378:         return ((String) object).length() != 0;
 379:       }
 380:     if (object instanceof Collection)
 381:       {
 382:         return ((Collection) object).size() != 0;
 383:       }
 384:     return false; // TODO user defined types
 385:   }
 386: 
 387:   /* -- 4.4 Number Functions -- */
 388: 
 389:   /**
 390:    * Implementation of the XPath <code>number</code> function.
 391:    */
 392:   public static double _number(Node context, Object object)
 393:   {
 394:     if (object == null)
 395:       {
 396:         object = Collections.singleton(context);
 397:       }
 398:     if (object instanceof Double)
 399:       {
 400:         return ((Double) object).doubleValue();
 401:       }
 402:     if (object instanceof Boolean)
 403:       {
 404:         return ((Boolean) object).booleanValue() ? 1.0 : 0.0;
 405:       }
 406:     if (object instanceof Collection)
 407:       {
 408:         // Convert node-set to string
 409:         object = stringValue((Collection) object);
 410:       }
 411:     if (object instanceof String)
 412:       {
 413:         String string = ((String) object).trim();
 414:         try
 415:           {
 416:             return Double.parseDouble(string);
 417:           }
 418:         catch (NumberFormatException e)
 419:           {
 420:             return Double.NaN;
 421:           }
 422:       }
 423:     return Double.NaN; // TODO user-defined types
 424:   }
 425: 
 426:   /**
 427:    * Computes the XPath string-value of the specified node-set.
 428:    */
 429:   public static String stringValue(Collection nodeSet)
 430:   {
 431:     StringBuffer buf = new StringBuffer();
 432:     for (Iterator i = nodeSet.iterator(); i.hasNext(); )
 433:       {
 434:         buf.append(stringValue((Node) i.next()));
 435:       }
 436:     return buf.toString();
 437:   }
 438: 
 439:   /**
 440:    * Computes the XPath string-value of the specified node.
 441:    */
 442:   public static String stringValue(Node node)
 443:   {
 444:     return stringValue(node, false);
 445:   }
 446:   
 447:   static String stringValue(Node node, boolean elementMode)
 448:   {
 449:     switch (node.getNodeType())
 450:       {
 451:       case Node.DOCUMENT_NODE: // 5.1 Root Node
 452:       case Node.DOCUMENT_FRAGMENT_NODE:
 453:       case Node.ELEMENT_NODE: // 5.2 Element Nodes
 454:         StringBuffer buf = new StringBuffer();
 455:         for (Node ctx = node.getFirstChild(); ctx != null;
 456:              ctx = ctx.getNextSibling())
 457:           {
 458:             buf.append(stringValue(ctx, true));
 459:           }
 460:         return buf.toString();
 461:       case Node.TEXT_NODE: // 5.7 Text Nodes
 462:       case Node.CDATA_SECTION_NODE:
 463:         return node.getNodeValue();
 464:       case Node.ATTRIBUTE_NODE: // 5.3 Attribute Nodes
 465:       case Node.PROCESSING_INSTRUCTION_NODE: // 5.5 Processing Instruction
 466:       case Node.COMMENT_NODE: // 5.6 Comment Nodes
 467:         if (!elementMode)
 468:           {
 469:             return node.getNodeValue();
 470:           }
 471:       default:
 472:         return "";
 473:       }
 474:   }
 475: 
 476: }