1. /*
  2. * Copyright 1999-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.jxpath.ri.model.dom;
  17. import java.util.HashMap;
  18. import java.util.Locale;
  19. import java.util.Map;
  20. import org.apache.commons.jxpath.AbstractFactory;
  21. import org.apache.commons.jxpath.JXPathContext;
  22. import org.apache.commons.jxpath.JXPathException;
  23. import org.apache.commons.jxpath.Pointer;
  24. import org.apache.commons.jxpath.ri.Compiler;
  25. import org.apache.commons.jxpath.ri.QName;
  26. import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
  27. import org.apache.commons.jxpath.ri.compiler.NodeTest;
  28. import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
  29. import org.apache.commons.jxpath.ri.compiler.ProcessingInstructionTest;
  30. import org.apache.commons.jxpath.ri.model.beans.NullPointer;
  31. import org.apache.commons.jxpath.ri.model.NodeIterator;
  32. import org.apache.commons.jxpath.ri.model.NodePointer;
  33. import org.apache.commons.jxpath.util.TypeUtils;
  34. import org.w3c.dom.Attr;
  35. import org.w3c.dom.Comment;
  36. import org.w3c.dom.Document;
  37. import org.w3c.dom.Element;
  38. import org.w3c.dom.NamedNodeMap;
  39. import org.w3c.dom.Node;
  40. import org.w3c.dom.NodeList;
  41. import org.w3c.dom.ProcessingInstruction;
  42. /**
  43. * A Pointer that points to a DOM node.
  44. *
  45. * @author Dmitri Plotnikov
  46. * @version $Revision: 1.24 $ $Date: 2004/06/29 22:58:17 $
  47. */
  48. public class DOMNodePointer extends NodePointer {
  49. private Node node;
  50. private Map namespaces;
  51. private String defaultNamespace;
  52. private String id;
  53. public static final String XML_NAMESPACE_URI =
  54. "http://www.w3.org/XML/1998/namespace";
  55. public static final String XMLNS_NAMESPACE_URI =
  56. "http://www.w3.org/2000/xmlns/";
  57. public DOMNodePointer(Node node, Locale locale) {
  58. super(null, locale);
  59. this.node = node;
  60. }
  61. public DOMNodePointer(Node node, Locale locale, String id) {
  62. super(null, locale);
  63. this.node = node;
  64. this.id = id;
  65. }
  66. public DOMNodePointer(NodePointer parent, Node node) {
  67. super(parent);
  68. this.node = node;
  69. }
  70. public boolean testNode(NodeTest test) {
  71. return testNode(node, test);
  72. }
  73. public static boolean testNode(Node node, NodeTest test) {
  74. if (test == null) {
  75. return true;
  76. }
  77. else if (test instanceof NodeNameTest) {
  78. if (node.getNodeType() != Node.ELEMENT_NODE) {
  79. return false;
  80. }
  81. NodeNameTest nodeNameTest = (NodeNameTest) test;
  82. QName testName = nodeNameTest.getNodeName();
  83. String namespaceURI = nodeNameTest.getNamespaceURI();
  84. boolean wildcard = nodeNameTest.isWildcard();
  85. String testPrefix = testName.getPrefix();
  86. if (wildcard && testPrefix == null) {
  87. return true;
  88. }
  89. if (wildcard
  90. || testName.getName()
  91. .equals(DOMNodePointer.getLocalName(node))) {
  92. String nodeNS = DOMNodePointer.getNamespaceURI(node);
  93. return equalStrings(namespaceURI, nodeNS);
  94. }
  95. }
  96. else if (test instanceof NodeTypeTest) {
  97. int nodeType = node.getNodeType();
  98. switch (((NodeTypeTest) test).getNodeType()) {
  99. case Compiler.NODE_TYPE_NODE :
  100. return nodeType == Node.ELEMENT_NODE;
  101. case Compiler.NODE_TYPE_TEXT :
  102. return nodeType == Node.CDATA_SECTION_NODE
  103. || nodeType == Node.TEXT_NODE;
  104. case Compiler.NODE_TYPE_COMMENT :
  105. return nodeType == Node.COMMENT_NODE;
  106. case Compiler.NODE_TYPE_PI :
  107. return nodeType == Node.PROCESSING_INSTRUCTION_NODE;
  108. }
  109. return false;
  110. }
  111. else if (test instanceof ProcessingInstructionTest) {
  112. if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
  113. String testPI = ((ProcessingInstructionTest) test).getTarget();
  114. String nodePI = ((ProcessingInstruction) node).getTarget();
  115. return testPI.equals(nodePI);
  116. }
  117. }
  118. return false;
  119. }
  120. private static boolean equalStrings(String s1, String s2) {
  121. if (s1 == null && s2 != null) {
  122. return false;
  123. }
  124. if (s1 != null && s2 == null) {
  125. return false;
  126. }
  127. if (s1 != null && !s1.trim().equals(s2.trim())) {
  128. return false;
  129. }
  130. return true;
  131. }
  132. public QName getName() {
  133. String ln = null;
  134. String ns = null;
  135. int type = node.getNodeType();
  136. if (type == Node.ELEMENT_NODE) {
  137. ns = DOMNodePointer.getPrefix(node);
  138. ln = DOMNodePointer.getLocalName(node);
  139. }
  140. else if (type == Node.PROCESSING_INSTRUCTION_NODE) {
  141. ln = ((ProcessingInstruction) node).getTarget();
  142. }
  143. return new QName(ns, ln);
  144. }
  145. public String getNamespaceURI() {
  146. return getNamespaceURI(node);
  147. }
  148. public NodeIterator childIterator(
  149. NodeTest test,
  150. boolean reverse,
  151. NodePointer startWith)
  152. {
  153. return new DOMNodeIterator(this, test, reverse, startWith);
  154. }
  155. public NodeIterator attributeIterator(QName name) {
  156. return new DOMAttributeIterator(this, name);
  157. }
  158. public NodePointer namespacePointer(String prefix) {
  159. return new NamespacePointer(this, prefix);
  160. }
  161. public NodeIterator namespaceIterator() {
  162. return new DOMNamespaceIterator(this);
  163. }
  164. public String getNamespaceURI(String prefix) {
  165. if (prefix == null || prefix.equals("")) {
  166. return getDefaultNamespaceURI();
  167. }
  168. if (prefix.equals("xml")) {
  169. return XML_NAMESPACE_URI;
  170. }
  171. if (prefix.equals("xmlns")) {
  172. return XMLNS_NAMESPACE_URI;
  173. }
  174. String namespace = null;
  175. if (namespaces == null) {
  176. namespaces = new HashMap();
  177. }
  178. else {
  179. namespace = (String) namespaces.get(prefix);
  180. }
  181. if (namespace == null) {
  182. String qname = "xmlns:" + prefix;
  183. Node aNode = node;
  184. if (aNode instanceof Document) {
  185. aNode = ((Document)aNode).getDocumentElement();
  186. }
  187. while (aNode != null) {
  188. if (aNode.getNodeType() == Node.ELEMENT_NODE) {
  189. Attr attr = ((Element) aNode).getAttributeNode(qname);
  190. if (attr != null) {
  191. namespace = attr.getValue();
  192. break;
  193. }
  194. }
  195. aNode = aNode.getParentNode();
  196. }
  197. if (namespace == null || namespace.equals("")) {
  198. namespace = NodePointer.UNKNOWN_NAMESPACE;
  199. }
  200. }
  201. namespaces.put(prefix, namespace);
  202. // TBD: We are supposed to resolve relative URIs to absolute ones.
  203. return namespace;
  204. }
  205. private String getNamespaceURI(String prefix, String namespace) {
  206. String qname = "xmlns:" + prefix;
  207. Node aNode = node;
  208. if (aNode instanceof Document) {
  209. aNode = ((Document)aNode).getDocumentElement();
  210. }
  211. while (aNode != null) {
  212. if (aNode.getNodeType() == Node.ELEMENT_NODE) {
  213. Attr attr = ((Element) aNode).getAttributeNode(qname);
  214. if (attr != null) {
  215. namespace = attr.getValue();
  216. break;
  217. }
  218. }
  219. aNode = aNode.getParentNode();
  220. }
  221. return namespace;
  222. }
  223. public String getDefaultNamespaceURI() {
  224. if (defaultNamespace == null) {
  225. Node aNode = node;
  226. while (aNode != null) {
  227. if (aNode.getNodeType() == Node.ELEMENT_NODE) {
  228. Attr attr = ((Element) aNode).getAttributeNode("xmlns");
  229. if (attr != null) {
  230. defaultNamespace = attr.getValue();
  231. break;
  232. }
  233. }
  234. aNode = aNode.getParentNode();
  235. }
  236. }
  237. if (defaultNamespace == null) {
  238. defaultNamespace = "";
  239. }
  240. // TBD: We are supposed to resolve relative URIs to absolute ones.
  241. return defaultNamespace.equals("") ? null : defaultNamespace;
  242. }
  243. public Object getBaseValue() {
  244. return node;
  245. }
  246. public Object getImmediateNode() {
  247. return node;
  248. }
  249. public boolean isActual() {
  250. return true;
  251. }
  252. public boolean isCollection() {
  253. return false;
  254. }
  255. public int getLength() {
  256. return 1;
  257. }
  258. public boolean isLeaf() {
  259. return !node.hasChildNodes();
  260. }
  261. /**
  262. * Returns true if the xml:lang attribute for the current node
  263. * or its parent has the specified prefix <i>lang</i>.
  264. * If no node has this prefix, calls <code>super.isLanguage(lang)</code>.
  265. */
  266. public boolean isLanguage(String lang) {
  267. String current = getLanguage();
  268. if (current == null) {
  269. return super.isLanguage(lang);
  270. }
  271. return current.toUpperCase().startsWith(lang.toUpperCase());
  272. }
  273. protected String getLanguage() {
  274. Node n = node;
  275. while (n != null) {
  276. if (n.getNodeType() == Node.ELEMENT_NODE) {
  277. Element e = (Element) n;
  278. String attr = e.getAttribute("xml:lang");
  279. if (attr != null && !attr.equals("")) {
  280. return attr;
  281. }
  282. }
  283. n = n.getParentNode();
  284. }
  285. return null;
  286. }
  287. /**
  288. * Sets contents of the node to the specified value. If the value is
  289. * a String, the contents of the node are replaced with this text.
  290. * If the value is an Element or Document, the children of the
  291. * node are replaced with the children of the passed node.
  292. */
  293. public void setValue(Object value) {
  294. if (node.getNodeType() == Node.TEXT_NODE
  295. || node.getNodeType() == Node.CDATA_SECTION_NODE) {
  296. String string = (String) TypeUtils.convert(value, String.class);
  297. if (string != null && !string.equals("")) {
  298. node.setNodeValue(string);
  299. }
  300. else {
  301. node.getParentNode().removeChild(node);
  302. }
  303. }
  304. else {
  305. NodeList children = node.getChildNodes();
  306. int count = children.getLength();
  307. for (int i = count; --i >= 0;) {
  308. Node child = children.item(i);
  309. node.removeChild(child);
  310. }
  311. if (value instanceof Node) {
  312. Node valueNode = (Node) value;
  313. if (valueNode instanceof Element
  314. || valueNode instanceof Document) {
  315. children = valueNode.getChildNodes();
  316. for (int i = 0; i < children.getLength(); i++) {
  317. Node child = children.item(i);
  318. node.appendChild(child.cloneNode(true));
  319. }
  320. }
  321. else {
  322. node.appendChild(valueNode.cloneNode(true));
  323. }
  324. }
  325. else {
  326. String string = (String) TypeUtils.convert(value, String.class);
  327. if (string != null && !string.equals("")) {
  328. Node textNode =
  329. node.getOwnerDocument().createTextNode(string);
  330. node.appendChild(textNode);
  331. }
  332. }
  333. }
  334. }
  335. public NodePointer createChild(
  336. JXPathContext context,
  337. QName name,
  338. int index)
  339. {
  340. if (index == WHOLE_COLLECTION) {
  341. index = 0;
  342. }
  343. boolean success =
  344. getAbstractFactory(context).createObject(
  345. context,
  346. this,
  347. node,
  348. name.toString(),
  349. index);
  350. if (success) {
  351. NodeTest nodeTest;
  352. String prefix = name.getPrefix();
  353. if (prefix != null) {
  354. String namespaceURI = context.getNamespaceURI(prefix);
  355. nodeTest = new NodeNameTest(name, namespaceURI);
  356. }
  357. else {
  358. nodeTest = new NodeNameTest(name);
  359. }
  360. NodeIterator it = childIterator(nodeTest, false, null);
  361. if (it != null && it.setPosition(index + 1)) {
  362. return it.getNodePointer();
  363. }
  364. }
  365. throw new JXPathException(
  366. "Factory could not create a child node for path: "
  367. + asPath()
  368. + "/"
  369. + name
  370. + "["
  371. + (index + 1)
  372. + "]");
  373. }
  374. public NodePointer createChild(JXPathContext context,
  375. QName name, int index, Object value)
  376. {
  377. NodePointer ptr = createChild(context, name, index);
  378. ptr.setValue(value);
  379. return ptr;
  380. }
  381. public NodePointer createAttribute(JXPathContext context, QName name) {
  382. if (!(node instanceof Element)) {
  383. return super.createAttribute(context, name);
  384. }
  385. Element element = (Element) node;
  386. String prefix = name.getPrefix();
  387. if (prefix != null) {
  388. String ns = getNamespaceURI(prefix);
  389. if (ns == null) {
  390. throw new JXPathException(
  391. "Unknown namespace prefix: " + prefix);
  392. }
  393. element.setAttributeNS(ns, name.toString(), "");
  394. }
  395. else {
  396. if (!element.hasAttribute(name.getName())) {
  397. element.setAttribute(name.getName(), "");
  398. }
  399. }
  400. NodeIterator it = attributeIterator(name);
  401. it.setPosition(1);
  402. return it.getNodePointer();
  403. }
  404. public void remove() {
  405. Node parent = node.getParentNode();
  406. if (parent == null) {
  407. throw new JXPathException("Cannot remove root DOM node");
  408. }
  409. parent.removeChild(node);
  410. }
  411. public String asPath() {
  412. if (id != null) {
  413. return "id('" + escape(id) + "')";
  414. }
  415. StringBuffer buffer = new StringBuffer();
  416. if (parent != null) {
  417. buffer.append(parent.asPath());
  418. }
  419. switch (node.getNodeType()) {
  420. case Node.ELEMENT_NODE :
  421. // If the parent pointer is not a DOMNodePointer, it is
  422. // the parent's responsibility to produce the node test part
  423. // of the path
  424. if (parent instanceof DOMNodePointer) {
  425. if (buffer.length() == 0
  426. || buffer.charAt(buffer.length() - 1) != '/') {
  427. buffer.append('/');
  428. }
  429. String nsURI = getNamespaceURI();
  430. String ln = DOMNodePointer.getLocalName(node);
  431. if (nsURI == null) {
  432. buffer.append(ln);
  433. buffer.append('[');
  434. buffer.append(getRelativePositionByName()).append(']');
  435. }
  436. else {
  437. String prefix = getNamespaceResolver().getPrefix(nsURI);
  438. if (prefix != null) {
  439. buffer.append(prefix);
  440. buffer.append(':');
  441. buffer.append(ln);
  442. buffer.append('[');
  443. buffer.append(getRelativePositionByName());
  444. buffer.append(']');
  445. }
  446. else {
  447. buffer.append("node()");
  448. buffer.append('[');
  449. buffer.append(getRelativePositionOfElement());
  450. buffer.append(']');
  451. }
  452. }
  453. }
  454. break;
  455. case Node.TEXT_NODE :
  456. case Node.CDATA_SECTION_NODE :
  457. buffer.append("/text()");
  458. buffer.append('[');
  459. buffer.append(getRelativePositionOfTextNode()).append(']');
  460. break;
  461. case Node.PROCESSING_INSTRUCTION_NODE :
  462. String target = ((ProcessingInstruction) node).getTarget();
  463. buffer.append("/processing-instruction(\'");
  464. buffer.append(target).append("')");
  465. buffer.append('[');
  466. buffer.append(getRelativePositionOfPI(target)).append(']');
  467. break;
  468. case Node.DOCUMENT_NODE :
  469. // That'll be empty
  470. }
  471. return buffer.toString();
  472. }
  473. private String escape(String string) {
  474. int index = string.indexOf('\'');
  475. while (index != -1) {
  476. string =
  477. string.substring(0, index)
  478. + "'"
  479. + string.substring(index + 1);
  480. index = string.indexOf('\'');
  481. }
  482. index = string.indexOf('\"');
  483. while (index != -1) {
  484. string =
  485. string.substring(0, index)
  486. + """
  487. + string.substring(index + 1);
  488. index = string.indexOf('\"');
  489. }
  490. return string;
  491. }
  492. private int getRelativePositionByName() {
  493. int count = 1;
  494. Node n = node.getPreviousSibling();
  495. while (n != null) {
  496. if (n.getNodeType() == Node.ELEMENT_NODE) {
  497. String nm = n.getNodeName();
  498. if (nm.equals(node.getNodeName())) {
  499. count++;
  500. }
  501. }
  502. n = n.getPreviousSibling();
  503. }
  504. return count;
  505. }
  506. private int getRelativePositionOfElement() {
  507. int count = 1;
  508. Node n = node.getPreviousSibling();
  509. while (n != null) {
  510. if (n.getNodeType() == Node.ELEMENT_NODE) {
  511. count++;
  512. }
  513. n = n.getPreviousSibling();
  514. }
  515. return count;
  516. }
  517. private int getRelativePositionOfTextNode() {
  518. int count = 1;
  519. Node n = node.getPreviousSibling();
  520. while (n != null) {
  521. if (n.getNodeType() == Node.TEXT_NODE
  522. || n.getNodeType() == Node.CDATA_SECTION_NODE) {
  523. count++;
  524. }
  525. n = n.getPreviousSibling();
  526. }
  527. return count;
  528. }
  529. private int getRelativePositionOfPI(String target) {
  530. int count = 1;
  531. Node n = node.getPreviousSibling();
  532. while (n != null) {
  533. if (n.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE
  534. && ((ProcessingInstruction) n).getTarget().equals(target)) {
  535. count++;
  536. }
  537. n = n.getPreviousSibling();
  538. }
  539. return count;
  540. }
  541. public int hashCode() {
  542. return System.identityHashCode(node);
  543. }
  544. public boolean equals(Object object) {
  545. if (object == this) {
  546. return true;
  547. }
  548. if (!(object instanceof DOMNodePointer)) {
  549. return false;
  550. }
  551. DOMNodePointer other = (DOMNodePointer) object;
  552. return node == other.node;
  553. }
  554. public static String getPrefix(Node node) {
  555. String prefix = node.getPrefix();
  556. if (prefix != null) {
  557. return prefix;
  558. }
  559. String name = node.getNodeName();
  560. int index = name.lastIndexOf(':');
  561. if (index == -1) {
  562. return null;
  563. }
  564. return name.substring(0, index);
  565. }
  566. public static String getLocalName(Node node) {
  567. String localName = node.getLocalName();
  568. if (localName != null) {
  569. return localName;
  570. }
  571. String name = node.getNodeName();
  572. int index = name.lastIndexOf(':');
  573. if (index == -1) {
  574. return name;
  575. }
  576. return name.substring(index + 1);
  577. }
  578. public static String getNamespaceURI(Node node) {
  579. if (node instanceof Document) {
  580. node = ((Document) node).getDocumentElement();
  581. }
  582. Element element = (Element) node;
  583. String uri = element.getNamespaceURI();
  584. if (uri != null) {
  585. return uri;
  586. }
  587. String qname;
  588. String prefix = getPrefix(node);
  589. if (prefix == null) {
  590. qname = "xmlns";
  591. }
  592. else {
  593. qname = "xmlns:" + prefix;
  594. }
  595. Node aNode = node;
  596. while (aNode != null) {
  597. if (aNode.getNodeType() == Node.ELEMENT_NODE) {
  598. Attr attr = ((Element) aNode).getAttributeNode(qname);
  599. if (attr != null) {
  600. return attr.getValue();
  601. }
  602. }
  603. aNode = aNode.getParentNode();
  604. }
  605. return null;
  606. }
  607. public Object getValue() {
  608. return stringValue(node);
  609. }
  610. private String stringValue(Node node) {
  611. int nodeType = node.getNodeType();
  612. if (nodeType == Node.COMMENT_NODE) {
  613. String text = ((Comment) node).getData();
  614. return text == null ? "" : text.trim();
  615. }
  616. else if (
  617. nodeType == Node.TEXT_NODE
  618. || nodeType == Node.CDATA_SECTION_NODE) {
  619. String text = node.getNodeValue();
  620. return text == null ? "" : text.trim();
  621. }
  622. else if (nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
  623. String text = ((ProcessingInstruction) node).getData();
  624. return text == null ? "" : text.trim();
  625. }
  626. else {
  627. NodeList list = node.getChildNodes();
  628. StringBuffer buf = new StringBuffer(16);
  629. for (int i = 0; i < list.getLength(); i++) {
  630. Node child = list.item(i);
  631. if (child.getNodeType() == Node.TEXT_NODE) {
  632. buf.append(child.getNodeValue());
  633. }
  634. else {
  635. buf.append(stringValue(child));
  636. }
  637. }
  638. return buf.toString().trim();
  639. }
  640. }
  641. /**
  642. * Locates a node by ID.
  643. */
  644. public Pointer getPointerByID(JXPathContext context, String id) {
  645. Document document;
  646. if (node.getNodeType() == Node.DOCUMENT_NODE) {
  647. document = (Document) node;
  648. }
  649. else {
  650. document = node.getOwnerDocument();
  651. }
  652. Element element = document.getElementById(id);
  653. if (element != null) {
  654. return new DOMNodePointer(element, getLocale(), id);
  655. }
  656. else {
  657. return new NullPointer(getLocale(), id);
  658. }
  659. }
  660. private AbstractFactory getAbstractFactory(JXPathContext context) {
  661. AbstractFactory factory = context.getFactory();
  662. if (factory == null) {
  663. throw new JXPathException(
  664. "Factory is not set on the JXPathContext - "
  665. + "cannot create path: "
  666. + asPath());
  667. }
  668. return factory;
  669. }
  670. public int compareChildNodePointers(
  671. NodePointer pointer1, NodePointer pointer2)
  672. {
  673. Node node1 = (Node) pointer1.getBaseValue();
  674. Node node2 = (Node) pointer2.getBaseValue();
  675. if (node1 == node2) {
  676. return 0;
  677. }
  678. int t1 = node1.getNodeType();
  679. int t2 = node2.getNodeType();
  680. if (t1 == Node.ATTRIBUTE_NODE && t2 != Node.ATTRIBUTE_NODE) {
  681. return -1;
  682. }
  683. else if (t1 != Node.ATTRIBUTE_NODE && t2 == Node.ATTRIBUTE_NODE) {
  684. return 1;
  685. }
  686. else if (t1 == Node.ATTRIBUTE_NODE && t2 == Node.ATTRIBUTE_NODE) {
  687. NamedNodeMap map = ((Node) getNode()).getAttributes();
  688. int length = map.getLength();
  689. for (int i = 0; i < length; i++) {
  690. Node n = map.item(i);
  691. if (n == node1) {
  692. return -1;
  693. }
  694. else if (n == node2) {
  695. return 1;
  696. }
  697. }
  698. return 0; // Should not happen
  699. }
  700. Node current = node.getFirstChild();
  701. while (current != null) {
  702. if (current == node1) {
  703. return -1;
  704. }
  705. else if (current == node2) {
  706. return 1;
  707. }
  708. current = current.getNextSibling();
  709. }
  710. return 0;
  711. }
  712. }