001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.jxpath.ri.model.jdom;
017:
018: import java.util.List;
019: import java.util.Locale;
020: import java.util.Map;
021:
022: import org.apache.commons.jxpath.AbstractFactory;
023: import org.apache.commons.jxpath.JXPathContext;
024: import org.apache.commons.jxpath.JXPathException;
025: import org.apache.commons.jxpath.ri.Compiler;
026: import org.apache.commons.jxpath.ri.QName;
027: import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
028: import org.apache.commons.jxpath.ri.compiler.NodeTest;
029: import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
030: import org.apache.commons.jxpath.ri.compiler.ProcessingInstructionTest;
031: import org.apache.commons.jxpath.ri.model.NodeIterator;
032: import org.apache.commons.jxpath.ri.model.NodePointer;
033: import org.apache.commons.jxpath.util.TypeUtils;
034: import org.jdom.Attribute;
035: import org.jdom.CDATA;
036: import org.jdom.Comment;
037: import org.jdom.Document;
038: import org.jdom.Element;
039: import org.jdom.Namespace;
040: import org.jdom.ProcessingInstruction;
041: import org.jdom.Text;
042:
043: /**
044: * A Pointer that points to a DOM node.
045: *
046: * @author Dmitri Plotnikov
047: * @version $Revision: 1.17 $ $Date: 2004/06/29 22:58:18 $
048: */
049: public class JDOMNodePointer extends NodePointer {
050: private Object node;
051: private Map namespaces;
052: private String defaultNamespace;
053: private String id;
054:
055: public static final String XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
056: public static final String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
057:
058: public JDOMNodePointer(Object node, Locale locale) {
059: super (null, locale);
060: this .node = node;
061: }
062:
063: public JDOMNodePointer(Object node, Locale locale, String id) {
064: super (null, locale);
065: this .node = node;
066: this .id = id;
067: }
068:
069: public JDOMNodePointer(NodePointer parent, Object node) {
070: super (parent);
071: this .node = node;
072: }
073:
074: public NodeIterator childIterator(NodeTest test, boolean reverse,
075: NodePointer startWith) {
076: return new JDOMNodeIterator(this , test, reverse, startWith);
077: }
078:
079: public NodeIterator attributeIterator(QName name) {
080: return new JDOMAttributeIterator(this , name);
081: }
082:
083: public NodeIterator namespaceIterator() {
084: return new JDOMNamespaceIterator(this );
085: }
086:
087: public NodePointer namespacePointer(String prefix) {
088: return new JDOMNamespacePointer(this , prefix);
089: }
090:
091: public String getNamespaceURI() {
092: return getNamespaceURI(node);
093: }
094:
095: private static String getNamespaceURI(Object node) {
096: if (node instanceof Element) {
097: Element element = (Element) node;
098: String ns = element.getNamespaceURI();
099: if (ns != null && ns.equals("")) {
100: ns = null;
101: }
102: return ns;
103: }
104: return null;
105: }
106:
107: public String getNamespaceURI(String prefix) {
108: if (node instanceof Document) {
109: Element element = ((Document) node).getRootElement();
110: Namespace ns = element.getNamespace(prefix);
111: if (ns != null) {
112: return ns.getURI();
113: }
114: } else if (node instanceof Element) {
115: Element element = (Element) node;
116: Namespace ns = element.getNamespace(prefix);
117: if (ns != null) {
118: return ns.getURI();
119: }
120: }
121: return null;
122: }
123:
124: public int compareChildNodePointers(NodePointer pointer1,
125: NodePointer pointer2) {
126: Object node1 = pointer1.getBaseValue();
127: Object node2 = pointer2.getBaseValue();
128: if (node1 == node2) {
129: return 0;
130: }
131:
132: if ((node1 instanceof Attribute)
133: && !(node2 instanceof Attribute)) {
134: return -1;
135: } else if (!(node1 instanceof Attribute)
136: && (node2 instanceof Attribute)) {
137: return 1;
138: } else if ((node1 instanceof Attribute)
139: && (node2 instanceof Attribute)) {
140: List list = ((Element) getNode()).getAttributes();
141: int length = list.size();
142: for (int i = 0; i < length; i++) {
143: Object n = list.get(i);
144: if (n == node1) {
145: return -1;
146: } else if (n == node2) {
147: return 1;
148: }
149: }
150: return 0; // Should not happen
151: }
152:
153: if (!(node instanceof Element)) {
154: throw new RuntimeException("JXPath internal error: "
155: + "compareChildNodes called for " + node);
156: }
157:
158: List children = ((Element) node).getContent();
159: int length = children.size();
160: for (int i = 0; i < length; i++) {
161: Object n = children.get(i);
162: if (n == node1) {
163: return -1;
164: } else if (n == node2) {
165: return 1;
166: }
167: }
168:
169: return 0;
170: }
171:
172: /**
173: * @see org.apache.commons.jxpath.ri.model.NodePointer#getBaseValue()
174: */
175: public Object getBaseValue() {
176: return node;
177: }
178:
179: public boolean isCollection() {
180: return false;
181: }
182:
183: public int getLength() {
184: return 1;
185: }
186:
187: public boolean isLeaf() {
188: if (node instanceof Element) {
189: return ((Element) node).getContent().size() == 0;
190: } else if (node instanceof Document) {
191: return ((Document) node).getContent().size() == 0;
192: }
193: return true;
194: }
195:
196: /**
197: * @see org.apache.commons.jxpath.ri.model.NodePointer#getName()
198: */
199: public QName getName() {
200: String ns = null;
201: String ln = null;
202: if (node instanceof Element) {
203: ns = ((Element) node).getNamespacePrefix();
204: if (ns != null && ns.equals("")) {
205: ns = null;
206: }
207: ln = ((Element) node).getName();
208: } else if (node instanceof ProcessingInstruction) {
209: ln = ((ProcessingInstruction) node).getTarget();
210: }
211: return new QName(ns, ln);
212: }
213:
214: /**
215: * @see org.apache.commons.jxpath.ri.model.NodePointer#getNode()
216: */
217: public Object getImmediateNode() {
218: return node;
219: }
220:
221: public Object getValue() {
222: if (node instanceof Element) {
223: return ((Element) node).getTextTrim();
224: } else if (node instanceof Comment) {
225: String text = ((Comment) node).getText();
226: if (text != null) {
227: text = text.trim();
228: }
229: return text;
230: } else if (node instanceof Text) {
231: return ((Text) node).getTextTrim();
232: } else if (node instanceof CDATA) {
233: return ((CDATA) node).getTextTrim();
234: } else if (node instanceof ProcessingInstruction) {
235: String text = ((ProcessingInstruction) node).getData();
236: if (text != null) {
237: text = text.trim();
238: }
239: return text;
240: }
241: return null;
242: }
243:
244: public void setValue(Object value) {
245: if (node instanceof Text) {
246: String string = (String) TypeUtils.convert(value,
247: String.class);
248: if (string != null && !string.equals("")) {
249: ((Text) node).setText(string);
250: } else {
251: nodeParent(node).removeContent((Text) node);
252: }
253: } else {
254: Element element = (Element) node;
255: element.getContent().clear();
256:
257: if (value instanceof Element) {
258: Element valueElement = (Element) value;
259: addContent(valueElement.getContent());
260: } else if (value instanceof Document) {
261: Document valueDocument = (Document) value;
262: addContent(valueDocument.getContent());
263: } else if (value instanceof Text || value instanceof CDATA) {
264: String string = ((Text) value).getText();
265: element.addContent(new Text(string));
266: } else if (value instanceof ProcessingInstruction) {
267: ProcessingInstruction pi = (ProcessingInstruction) ((ProcessingInstruction) value)
268: .clone();
269: element.addContent(pi);
270: } else if (value instanceof Comment) {
271: Comment comment = (Comment) ((Comment) value).clone();
272: element.addContent(comment);
273: } else {
274: String string = (String) TypeUtils.convert(value,
275: String.class);
276: if (string != null && !string.equals("")) {
277: element.addContent(new Text(string));
278: }
279: }
280: }
281: }
282:
283: private void addContent(List content) {
284: Element element = (Element) node;
285: int count = content.size();
286:
287: for (int i = 0; i < count; i++) {
288: Object child = content.get(i);
289: if (child instanceof Element) {
290: child = ((Element) child).clone();
291: element.addContent((Element) child);
292: } else if (child instanceof Text) {
293: child = ((Text) child).clone();
294: element.addContent((Text) child);
295: } else if (node instanceof CDATA) {
296: child = ((CDATA) child).clone();
297: element.addContent((CDATA) child);
298: } else if (node instanceof ProcessingInstruction) {
299: child = ((ProcessingInstruction) child).clone();
300: element.addContent((ProcessingInstruction) child);
301: } else if (node instanceof Comment) {
302: child = ((Comment) child).clone();
303: element.addContent((Comment) child);
304: }
305: }
306: }
307:
308: public boolean testNode(NodeTest test) {
309: return testNode(this , node, test);
310: }
311:
312: public static boolean testNode(NodePointer pointer, Object node,
313: NodeTest test) {
314: if (test == null) {
315: return true;
316: } else if (test instanceof NodeNameTest) {
317: if (!(node instanceof Element)) {
318: return false;
319: }
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:
330: if (wildcard
331: || testName.getName().equals(
332: JDOMNodePointer.getLocalName(node))) {
333: String nodeNS = JDOMNodePointer.getNamespaceURI(node);
334: return equalStrings(namespaceURI, nodeNS);
335: }
336:
337: } else if (test instanceof NodeTypeTest) {
338: switch (((NodeTypeTest) test).getNodeType()) {
339: case Compiler.NODE_TYPE_NODE:
340: return node instanceof Element;
341: case Compiler.NODE_TYPE_TEXT:
342: return (node instanceof Text)
343: || (node instanceof CDATA);
344: case Compiler.NODE_TYPE_COMMENT:
345: return node instanceof Comment;
346: case Compiler.NODE_TYPE_PI:
347: return node instanceof ProcessingInstruction;
348: }
349: return false;
350: } else if (test instanceof ProcessingInstructionTest) {
351: if (node instanceof ProcessingInstruction) {
352: String testPI = ((ProcessingInstructionTest) test)
353: .getTarget();
354: String nodePI = ((ProcessingInstruction) node)
355: .getTarget();
356: return testPI.equals(nodePI);
357: }
358: }
359:
360: return false;
361: }
362:
363: private static boolean equalStrings(String s1, String s2) {
364: if (s1 == null && s2 != null) {
365: return false;
366: }
367: if (s1 != null && s2 == null) {
368: return false;
369: }
370:
371: if (s1 != null && !s1.trim().equals(s2.trim())) {
372: return false;
373: }
374:
375: return true;
376: }
377:
378: public static String getPrefix(Object node) {
379: if (node instanceof Element) {
380: String prefix = ((Element) node).getNamespacePrefix();
381: return (prefix == null || prefix.equals("")) ? null
382: : prefix;
383: } else if (node instanceof Attribute) {
384: String prefix = ((Attribute) node).getNamespacePrefix();
385: return (prefix == null || prefix.equals("")) ? null
386: : prefix;
387: }
388: return null;
389: }
390:
391: public static String getLocalName(Object node) {
392: if (node instanceof Element) {
393: return ((Element) node).getName();
394: } else if (node instanceof Attribute) {
395: return ((Attribute) node).getName();
396: }
397: return null;
398: }
399:
400: /**
401: * Returns true if the xml:lang attribute for the current node
402: * or its parent has the specified prefix <i>lang</i>.
403: * If no node has this prefix, calls <code>super.isLanguage(lang)</code>.
404: */
405: public boolean isLanguage(String lang) {
406: String current = getLanguage();
407: if (current == null) {
408: return super .isLanguage(lang);
409: }
410: return current.toUpperCase().startsWith(lang.toUpperCase());
411: }
412:
413: protected String getLanguage() {
414: Object n = node;
415: while (n != null) {
416: if (n instanceof Element) {
417: Element e = (Element) n;
418: String attr = e.getAttributeValue("lang",
419: Namespace.XML_NAMESPACE);
420: if (attr != null && !attr.equals("")) {
421: return attr;
422: }
423: }
424: n = nodeParent(n);
425: }
426: return null;
427: }
428:
429: private Element nodeParent(Object node) {
430: if (node instanceof Element) {
431: Object parent = ((Element) node).getParent();
432: if (parent instanceof Element) {
433: return (Element) parent;
434: }
435: } else if (node instanceof Text) {
436: return (Element) ((Text) node).getParent();
437: } else if (node instanceof CDATA) {
438: return (Element) ((CDATA) node).getParent();
439: } else if (node instanceof ProcessingInstruction) {
440: return (Element) ((ProcessingInstruction) node).getParent();
441: } else if (node instanceof Comment) {
442: return (Element) ((Comment) node).getParent();
443: }
444: return null;
445: }
446:
447: public NodePointer createChild(JXPathContext context, QName name,
448: int index) {
449: if (index == WHOLE_COLLECTION) {
450: index = 0;
451: }
452: boolean success = getAbstractFactory(context).createObject(
453: context, this , node, name.toString(), index);
454: if (success) {
455: NodeTest nodeTest;
456: String prefix = name.getPrefix();
457: if (prefix != null) {
458: String namespaceURI = context.getNamespaceURI(prefix);
459: nodeTest = new NodeNameTest(name, namespaceURI);
460: } else {
461: nodeTest = new NodeNameTest(name);
462: }
463:
464: NodeIterator it = childIterator(nodeTest, false, null);
465: if (it != null && it.setPosition(index + 1)) {
466: return it.getNodePointer();
467: }
468: }
469: throw new JXPathException("Factory could not create "
470: + "a child node for path: " + asPath() + "/" + name
471: + "[" + (index + 1) + "]");
472: }
473:
474: public NodePointer createChild(JXPathContext context, QName name,
475: int index, Object value) {
476: NodePointer ptr = createChild(context, name, index);
477: ptr.setValue(value);
478: return ptr;
479: }
480:
481: public NodePointer createAttribute(JXPathContext context, QName name) {
482: if (!(node instanceof Element)) {
483: return super .createAttribute(context, name);
484: }
485:
486: Element element = (Element) node;
487: String prefix = name.getPrefix();
488: if (prefix != null) {
489: Namespace ns = element.getNamespace(prefix);
490: if (ns == null) {
491: throw new JXPathException("Unknown namespace prefix: "
492: + prefix);
493: }
494: Attribute attr = element.getAttribute(name.getName(), ns);
495: if (attr == null) {
496: element.setAttribute(name.getName(), "", ns);
497: }
498: } else {
499: Attribute attr = element.getAttribute(name.getName());
500: if (attr == null) {
501: element.setAttribute(name.getName(), "");
502: }
503: }
504: NodeIterator it = attributeIterator(name);
505: it.setPosition(1);
506: return it.getNodePointer();
507: }
508:
509: public void remove() {
510: Element parent = nodeParent(node);
511: if (parent == null) {
512: throw new JXPathException("Cannot remove root JDOM node");
513: }
514: parent.getContent().remove(node);
515: }
516:
517: public String asPath() {
518: if (id != null) {
519: return "id('" + escape(id) + "')";
520: }
521:
522: StringBuffer buffer = new StringBuffer();
523: if (parent != null) {
524: buffer.append(parent.asPath());
525: }
526: if (node instanceof Element) {
527: // If the parent pointer is not a JDOMNodePointer, it is
528: // the parent's responsibility to produce the node test part
529: // of the path
530: if (parent instanceof JDOMNodePointer) {
531: if (buffer.length() == 0
532: || buffer.charAt(buffer.length() - 1) != '/') {
533: buffer.append('/');
534: }
535: String nsURI = getNamespaceURI();
536: String ln = JDOMNodePointer.getLocalName(node);
537:
538: if (nsURI == null) {
539: buffer.append(ln);
540: buffer.append('[');
541: buffer.append(getRelativePositionByName()).append(
542: ']');
543: } else {
544: String prefix = getNamespaceResolver().getPrefix(
545: 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: } else {
554: buffer.append("node()");
555: buffer.append('[');
556: buffer.append(getRelativePositionOfElement());
557: buffer.append(']');
558: }
559: }
560:
561: }
562: } else if (node instanceof Text || node instanceof CDATA) {
563: buffer.append("/text()");
564: buffer.append('[').append(getRelativePositionOfTextNode())
565: .append(']');
566: } else if (node instanceof ProcessingInstruction) {
567: String target = ((ProcessingInstruction) node).getTarget();
568: buffer.append("/processing-instruction(\'").append(target)
569: .append("')");
570: buffer.append('[').append(getRelativePositionOfPI(target))
571: .append(']');
572: }
573: return buffer.toString();
574: }
575:
576: private String escape(String string) {
577: int index = string.indexOf('\'');
578: while (index != -1) {
579: string = string.substring(0, index) + "'"
580: + string.substring(index + 1);
581: index = string.indexOf('\'');
582: }
583: index = string.indexOf('\"');
584: while (index != -1) {
585: string = string.substring(0, index) + """
586: + string.substring(index + 1);
587: index = string.indexOf('\"');
588: }
589: return string;
590: }
591:
592: private int getRelativePositionByName() {
593: if (node instanceof Element) {
594: Object parent = ((Element) node).getParent();
595: if (!(parent instanceof Element)) {
596: return 1;
597: }
598:
599: List children = ((Element) parent).getContent();
600: int count = 0;
601: String name = ((Element) node).getQualifiedName();
602: for (int i = 0; i < children.size(); i++) {
603: Object child = children.get(i);
604: if ((child instanceof Element)
605: && ((Element) child).getQualifiedName().equals(
606: name)) {
607: count++;
608: }
609: if (child == node) {
610: break;
611: }
612: }
613: return count;
614: }
615: return 1;
616: }
617:
618: private int getRelativePositionOfElement() {
619: Element parent = (Element) ((Element) node).getParent();
620: if (parent == null) {
621: return 1;
622: }
623: List children = parent.getContent();
624: int count = 0;
625: for (int i = 0; i < children.size(); i++) {
626: Object child = children.get(i);
627: if (child instanceof Element) {
628: count++;
629: }
630: if (child == node) {
631: break;
632: }
633: }
634: return count;
635: }
636:
637: private int getRelativePositionOfTextNode() {
638: Element parent;
639: if (node instanceof Text) {
640: parent = (Element) ((Text) node).getParent();
641: } else {
642: parent = (Element) ((CDATA) node).getParent();
643: }
644: if (parent == null) {
645: return 1;
646: }
647: List children = parent.getContent();
648: int count = 0;
649: for (int i = 0; i < children.size(); i++) {
650: Object child = children.get(i);
651: if (child instanceof Text || child instanceof CDATA) {
652: count++;
653: }
654: if (child == node) {
655: break;
656: }
657: }
658: return count;
659: }
660:
661: private int getRelativePositionOfPI(String target) {
662: Element parent = (Element) ((ProcessingInstruction) node)
663: .getParent();
664: if (parent == null) {
665: return 1;
666: }
667: List children = parent.getContent();
668: int count = 0;
669: for (int i = 0; i < children.size(); i++) {
670: Object child = children.get(i);
671: if (child instanceof ProcessingInstruction
672: && (target == null || target
673: .equals(((ProcessingInstruction) child)
674: .getTarget()))) {
675: count++;
676: }
677: if (child == node) {
678: break;
679: }
680: }
681: return count;
682: }
683:
684: public int hashCode() {
685: return System.identityHashCode(node);
686: }
687:
688: public boolean equals(Object object) {
689: if (object == this ) {
690: return true;
691: }
692:
693: if (!(object instanceof JDOMNodePointer)) {
694: return false;
695: }
696:
697: JDOMNodePointer other = (JDOMNodePointer) object;
698: return node == other.node;
699: }
700:
701: private AbstractFactory getAbstractFactory(JXPathContext context) {
702: AbstractFactory factory = context.getFactory();
703: if (factory == null) {
704: throw new JXPathException(
705: "Factory is not set on the JXPathContext - cannot create path: "
706: + asPath());
707: }
708: return factory;
709: }
710: }
|