1 /* Copyright 2005 Elliotte Rusty Harold 2 3 This library is free software; you can redistribute it and/or modify 4 it under the terms of version 2.1 of the GNU Lesser General Public 5 License as published by the Free Software Foundation. 6 7 This library is distributed in the hope that it will be useful, 8 but WITHOUT ANY WARRANTY; without even the implied warranty of 9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 GNU Lesser General Public License for more details. 11 12 You should have received a copy of the GNU Lesser General Public 13 License along with this library; if not, write to the 14 Free Software Foundation, Inc., 59 Temple Place, Suite 330, 15 Boston, MA 02111-1307 USA 16 17 You can contact Elliotte Rusty Harold by sending e-mail to 18 elharo@ibiblio.org. Please include the word "XOM" in the 19 subject line. The XOM home page is located at http://www.xom.nu/ 20 */ 21 22 package nu.xom; 23 24 /** 25 * <p> 26 * Interface between Jaxen and XOM. 27 * </p> 28 * 29 * @author Elliotte Rusty Harold 30 * @version 1.1.1b1 31 * 32 */ 33 34 import org.jaxen.DefaultNavigator; 35 import org.jaxen.FunctionCallException; 36 import org.jaxen.JaxenConstants; 37 import org.jaxen.JaxenException; 38 import org.jaxen.NamedAccessNavigator; 39 import org.jaxen.UnsupportedAxisException; 40 import org.jaxen.XPath; 41 import org.jaxen.util.SingleObjectIterator; 42 43 import java.util.Iterator; 44 import java.util.List; 45 import java.util.ArrayList; 46 import java.util.Map; 47 import java.util.NoSuchElementException; 48 49 50 class JaxenNavigator extends DefaultNavigator implements NamedAccessNavigator { 51 52 53 private static final long serialVersionUID = 7008740797833836742L; 54 55 getSelfAxisIterator(Object contextNode)56 public Iterator getSelfAxisIterator(Object contextNode) { 57 58 if (contextNode instanceof Text) { 59 // wrap text nodes in a list 60 Text node = (Text) contextNode; 61 ArrayList temp = new ArrayList(); 62 ParentNode parent = node.getParent(); 63 // parent is never null here due to DocumentFragment 64 int index = parent.indexOf(node); 65 int first = index; 66 int last = index; 67 while (first > 0 && parent.getChild(first-1).isText()) { 68 first--; 69 } 70 while (last < parent.getChildCount()-1 && parent.getChild(last+1).isText()) { 71 last++; 72 } 73 for (int i = first; i <= last; i++) { 74 temp.add(parent.getChild(i)); 75 } 76 contextNode = temp; 77 } 78 return new SingleObjectIterator(contextNode); 79 80 } 81 82 getElementById(Object node, String id)83 public Object getElementById(Object node, String id) { 84 85 Node original; 86 if (node instanceof ArrayList) { 87 original = (Node) ((List) node).get(0); 88 } 89 else { 90 original = (Node) node; 91 } 92 ParentNode parent; 93 if (original.isElement() || original.isDocument()) { 94 parent = (ParentNode) original; 95 } 96 else { 97 parent = original.getParent(); 98 } 99 100 // find highest parent node 101 ParentNode high = parent; 102 while (parent != null) { 103 high = parent; 104 parent = parent.getParent(); 105 } 106 107 // Now search down from the highest point for the requested ID 108 Element root; 109 if (high.isDocument()) { 110 root = ((Document) high).getRootElement(); 111 } 112 else { // document fragment 113 Node first = high.getChild(0); 114 if (first.isElement()) { 115 root = (Element) high.getChild(0); 116 } 117 else { 118 return null; 119 } 120 } 121 122 return findByID(root, id); 123 124 } 125 126 127 // ????remove recursion findByID(Element top, String id)128 public static Element findByID(Element top, String id) { 129 130 if (hasID(top, id)) return top; 131 else { 132 Elements children = top.getChildElements(); 133 for (int i = 0; i < children.size(); i++) { 134 Element result = findByID(children.get(i), id); 135 if (result != null) return result; 136 } 137 } 138 return null; 139 140 } 141 142 hasID(Element top, String id)143 private static boolean hasID(Element top, String id) { 144 145 int count = top.getAttributeCount(); 146 for (int i = 0; i < count; i++) { 147 Attribute a = top.getAttribute(i); 148 if (Attribute.Type.ID == a.getType()) { 149 // Do not need to fully normalize here 150 // because if the value passed to the id() function 151 // contains any spaces; then it is converted into a 152 // search for multiple IDs, none of which have spaces 153 return a.getValue().trim().equals(id); 154 } 155 } 156 return false; 157 158 } 159 160 getNamespacePrefix(Object o)161 public String getNamespacePrefix(Object o) { 162 Namespace ns = (Namespace) o; 163 return ns.getPrefix(); 164 } 165 166 getNamespaceStringValue(Object o)167 public String getNamespaceStringValue(Object o) { 168 Namespace ns = (Namespace) o; 169 return ns.getValue(); 170 } 171 172 getNamespaceAxisIterator(Object contextNode)173 public Iterator getNamespaceAxisIterator(Object contextNode) { 174 175 try { 176 Element element = (Element) contextNode; 177 // ???? can probably avoid this list copy 178 Map bindings = element.getNamespacePrefixesInScope(); 179 Iterator iterator = bindings.entrySet().iterator(); 180 List result = new ArrayList(bindings.size()+1); 181 result.add(new Namespace("xml", 182 "http://www.w3.org/XML/1998/namespace", element)); 183 184 while (iterator.hasNext()) { 185 Map.Entry binding = (Map.Entry) iterator.next(); 186 String prefix = (String) binding.getKey(); 187 String uri = (String) binding.getValue(); 188 if (! "".equals(prefix) || ! "".equals(uri)) { 189 Namespace ns = new Namespace(prefix, uri, element); 190 result.add(ns); 191 } 192 } 193 return result.iterator(); 194 } 195 catch (ClassCastException ex) { 196 return JaxenConstants.EMPTY_ITERATOR; 197 } 198 199 } 200 201 getParentAxisIterator(Object contextNode)202 public Iterator getParentAxisIterator(Object contextNode) { 203 204 Node parent = (Node) getParentNode(contextNode); 205 if (parent == null) return JaxenConstants.EMPTY_ITERATOR; 206 else return new SingleObjectIterator(parent); 207 208 } 209 210 getDocumentNode(Object o)211 public Object getDocumentNode(Object o) { 212 213 Node node = (Node) o; 214 return node.getRoot(); 215 216 } 217 218 getDocument(String url)219 public Object getDocument(String url) throws FunctionCallException { 220 throw new FunctionCallException("document() function not supported"); 221 } 222 getAttributeAxisIterator(Object contextNode)223 public Iterator getAttributeAxisIterator(Object contextNode) { 224 225 try { 226 Element element = (Element) contextNode; 227 return element.attributeIterator(); 228 } 229 catch (ClassCastException ex) { 230 return JaxenConstants.EMPTY_ITERATOR; 231 } 232 233 } 234 235 getChildAxisIterator(Object o)236 public Iterator getChildAxisIterator(Object o) { 237 238 if (o instanceof ParentNode) { 239 return new ChildIterator((ParentNode) o); 240 } 241 else { 242 return JaxenConstants.EMPTY_ITERATOR; 243 } 244 245 } 246 247 getFollowingSiblingAxisIterator(Object o)248 public Iterator getFollowingSiblingAxisIterator(Object o) { 249 250 Node start; 251 if (o instanceof ArrayList) { 252 List list = (ArrayList) o; 253 start = (Node) list.get(list.size()-1); 254 } 255 else { 256 start = (Node) o; 257 } 258 ParentNode parent = start.getParent(); 259 if (parent == null) return JaxenConstants.EMPTY_ITERATOR; 260 int startPos = parent.indexOf(start) + 1; 261 return new ChildIterator(parent, startPos); 262 263 } 264 265 getParentNode(Object o)266 public Object getParentNode(Object o) { 267 268 Node n; 269 if (o instanceof ArrayList) { 270 n = (Node) ((List) o).get(0); 271 } 272 else { 273 n = (Node) o; 274 } 275 return n.getParent(); 276 277 } 278 279 getTextStringValue(Object o)280 public String getTextStringValue(Object o) { 281 282 List texts = (List) o; 283 if (texts.size() == 1) { 284 return ((Text) texts.get(0)).getValue(); 285 } 286 else { 287 StringBuffer result = new StringBuffer(); 288 Iterator iterator = texts.iterator(); 289 while (iterator.hasNext()) { 290 Text text = (Text) iterator.next(); 291 result.append(text.getValue()); 292 } 293 return result.toString(); 294 } 295 296 } 297 298 299 private static class ChildIterator implements Iterator { 300 301 private final ParentNode parent; 302 303 private int xomIndex = 0; 304 private final int xomCount; 305 ChildIterator(ParentNode parent)306 ChildIterator(ParentNode parent) { 307 this.parent = parent; 308 this.xomCount = parent.getChildCount(); 309 } 310 311 ChildIterator(ParentNode parent, int startNode)312 ChildIterator(ParentNode parent, int startNode) { 313 this.parent = parent; 314 this.xomIndex = startNode; 315 this.xomCount = parent.getChildCount(); 316 } 317 318 hasNext()319 public boolean hasNext() { 320 321 for (int i = xomIndex; i < xomCount; i++) { 322 Node next = parent.getChild(i); 323 if (next.isText()) { 324 if (! ((Text) next).isEmpty()) { 325 return true; 326 } 327 } 328 else return true; 329 } 330 return false; 331 332 } 333 334 next()335 public Object next() { 336 337 Object result; 338 Node next = parent.getChild(xomIndex++); 339 if (next.isText()) { 340 Text t = (Text) next; 341 // Is this an empty text node? 342 boolean empty = t.isEmpty(); 343 List texts = new ArrayList(1); 344 texts.add(t); 345 while (xomIndex < xomCount) { 346 Node nextText = parent.getChild(xomIndex); 347 if (! nextText.isText()) break; 348 xomIndex++; 349 texts.add(nextText); 350 if (empty) { 351 if (! ((Text) nextText).isEmpty()) { 352 empty = false; 353 } 354 } 355 } 356 // need to make sure at least one of these texts is non-empty 357 if (empty) return next(); 358 else result = texts; 359 } 360 else if (next.isDocType()) { 361 return next(); 362 } 363 else { 364 result = next; 365 } 366 return result; 367 368 } 369 remove()370 public void remove() { 371 throw new UnsupportedOperationException(); 372 } 373 374 } 375 376 377 private static class NamedChildIterator implements Iterator { 378 379 private final ParentNode parent; 380 381 private int index = -1; 382 private final int xomCount; 383 private Element next; 384 private final String localName; 385 private final String URI; 386 NamedChildIterator(ParentNode parent, String localName, String prefix, String namespaceURI)387 NamedChildIterator(ParentNode parent, String localName, String prefix, String namespaceURI) { 388 this.parent = parent; 389 this.xomCount = parent.getChildCount(); 390 this.localName = localName; 391 if (namespaceURI == null) this.URI = ""; 392 else this.URI = namespaceURI; 393 394 findNext(); 395 } 396 findNext()397 private void findNext() { 398 399 while (++index < xomCount) { 400 Node next = parent.getChild(index); 401 if (next.isElement()) { 402 Element element = (Element) next; 403 String elementNamespace = element.getNamespaceURI(); 404 if (elementNamespace.equals(URI)) { 405 // I don't need to worry about the prefix here because XPath only iterates 406 // by local name and namespace URI. The prefix doesn't matter. 407 // This does assume that this class is non-public. 408 if (element.getLocalName().equals(localName)) { 409 this.next = element; 410 return; 411 } 412 } 413 } 414 } 415 next = null; 416 } 417 hasNext()418 public boolean hasNext() { 419 return next != null; 420 } 421 422 next()423 public Object next() { 424 425 if (next == null) throw new NoSuchElementException(); // Correct? Yes. Necessary???? 426 Object result = next; 427 findNext(); 428 return result; 429 } 430 remove()431 public void remove() { 432 throw new UnsupportedOperationException(); 433 } 434 435 } 436 437 getElementNamespaceUri(Object element)438 public String getElementNamespaceUri(Object element) { 439 return ((Element) element).getNamespaceURI(); 440 } 441 442 443 // In Jaxen, name means the local name only getElementName(Object element)444 public String getElementName(Object element) { 445 return ((Element) element).getLocalName(); 446 } 447 getElementQName(Object element)448 public String getElementQName(Object element) { 449 return ((Element) element).getQualifiedName(); 450 } 451 452 getAttributeNamespaceUri(Object attr)453 public String getAttributeNamespaceUri(Object attr) { 454 Attribute attribute = (Attribute) attr; 455 return attribute.getNamespaceURI(); 456 } 457 458 459 // In Jaxen, name means the local name only getAttributeName(Object attr)460 public String getAttributeName(Object attr) { 461 Attribute attribute = (Attribute) attr; 462 return attribute.getLocalName(); 463 } 464 465 getAttributeQName(Object attr)466 public String getAttributeQName(Object attr) { 467 Attribute attribute = (Attribute) attr; 468 return attribute.getQualifiedName(); 469 } 470 getProcessingInstructionData(Object o)471 public String getProcessingInstructionData(Object o) { 472 ProcessingInstruction pi = (ProcessingInstruction) o; 473 return pi.getValue(); 474 } 475 476 getProcessingInstructionTarget(Object o)477 public String getProcessingInstructionTarget(Object o) { 478 ProcessingInstruction pi = (ProcessingInstruction) o; 479 return pi.getTarget(); 480 } 481 482 isDocument(Object object)483 public boolean isDocument(Object object) { 484 return object instanceof Document || object instanceof DocumentFragment; 485 } 486 487 isElement(Object object)488 public boolean isElement(Object object) { 489 return object instanceof Element; 490 } 491 492 isAttribute(Object object)493 public boolean isAttribute(Object object) { 494 return object instanceof Attribute; 495 } 496 497 isNamespace(Object object)498 public boolean isNamespace(Object object) { 499 return object instanceof Namespace; 500 } 501 502 isComment(Object object)503 public boolean isComment(Object object) { 504 return object instanceof Comment; 505 } 506 507 isText(Object object)508 public boolean isText(Object object) { 509 // ???? hack: need to use a separate special subclass of ArrayList I can identify. 510 // But may not be necessary since this is not a public API. I don't 511 // think there's any way to get a different ArrayList into this method. 512 if (object instanceof ArrayList) { 513 Iterator iterator = ((List) object).iterator(); 514 while (iterator.hasNext()) { 515 if (! (iterator.next() instanceof Text)) return false; 516 } 517 return true; 518 } 519 return false; 520 } 521 522 isProcessingInstruction(Object object)523 public boolean isProcessingInstruction(Object object) { 524 return object instanceof ProcessingInstruction; 525 } 526 527 getCommentStringValue(Object comment)528 public String getCommentStringValue(Object comment) { 529 return ((Comment) comment).getValue(); 530 } 531 532 getElementStringValue(Object element)533 public String getElementStringValue(Object element) { 534 return ((Element) element).getValue(); 535 } 536 537 getAttributeStringValue(Object attribute)538 public String getAttributeStringValue(Object attribute) { 539 return ((Attribute) attribute).getValue(); 540 } 541 542 parseXPath(String expression)543 public XPath parseXPath(String expression) throws JaxenException { 544 return new JaxenConnector(expression); 545 } 546 547 getChildAxisIterator(Object parent, String localName, String namespacePrefix, String namespaceURI)548 public Iterator getChildAxisIterator(Object parent, String localName, String namespacePrefix, String namespaceURI) 549 throws UnsupportedAxisException { 550 551 if (parent instanceof ParentNode) { 552 return new NamedChildIterator((ParentNode) parent, localName, namespacePrefix, namespaceURI); 553 } 554 return JaxenConstants.EMPTY_ITERATOR; 555 556 } 557 558 getAttributeAxisIterator(Object contextNode, String localName, String namespacePrefix, String namespaceURI)559 public Iterator getAttributeAxisIterator(Object contextNode, String localName, String namespacePrefix, String namespaceURI) 560 throws UnsupportedAxisException { 561 562 // I don't need to worry about the prefix here because XPath only iterates 563 // by local name and namespace URI. The prefix doesn't matter. 564 // This does assume that this class is non-public. 565 try { 566 Element element = (Element) contextNode; 567 Attribute result = null; 568 if (namespaceURI == null) { 569 result = element.getAttribute(localName); 570 } 571 else { 572 result = element.getAttribute(localName, namespaceURI); 573 } 574 575 if (result == null) return JaxenConstants.EMPTY_ITERATOR; 576 577 return new SingleObjectIterator(result); 578 } 579 catch (ClassCastException ex) { 580 return JaxenConstants.EMPTY_ITERATOR; 581 } 582 583 } 584 585 586 } 587