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