001: /* ***** BEGIN LICENSE BLOCK *****
002: * Version: MPL 1.1/GPL 2.0
003: *
004: * The contents of this file are subject to the Mozilla Public License Version
005: * 1.1 (the "License"); you may not use this file except in compliance with
006: * the License. You may obtain a copy of the License at
007: * http://www.mozilla.org/MPL/
008: *
009: * Software distributed under the License is distributed on an "AS IS" basis,
010: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011: * for the specific language governing rights and limitations under the
012: * License.
013: *
014: * The Original Code is Rhino DOM-only E4X implementation.
015: *
016: * The Initial Developer of the Original Code is
017: * David P. Caldwell.
018: * Portions created by David P. Caldwell are Copyright (C)
019: * 2007 David P. Caldwell. All Rights Reserved.
020: *
021: *
022: * Contributor(s):
023: * David P. Caldwell <inonit@inonit.com>
024: *
025: * Alternatively, the contents of this file may be used under the terms of
026: * the GNU General Public License Version 2 or later (the "GPL"), in which
027: * case the provisions of the GPL are applicable instead of those above. If
028: * you wish to allow use of your version of this file only under the terms of
029: * the GPL and not to allow others to use your version of this file under the
030: * MPL, indicate your decision by deleting the provisions above and replacing
031: * them with the notice and other provisions required by the GPL. If you do
032: * not delete the provisions above, a recipient may use your version of this
033: * file under either the MPL or the GPL.
034: *
035: * ***** END LICENSE BLOCK ***** */
036:
037: package org.mozilla.javascript.xmlimpl;
038:
039: import java.util.*;
040:
041: import org.w3c.dom.*;
042:
043: import org.mozilla.javascript.*;
044:
045: // Disambiguate with org.mozilla.javascript
046: import org.w3c.dom.Node;
047:
048: class XmlNode {
049: private static final String XML_NAMESPACES_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
050:
051: private static final String USER_DATA_XMLNODE_KEY = XmlNode.class
052: .getName();
053:
054: private static final boolean DOM_LEVEL_3 = true;
055:
056: private static XmlNode getUserData(Node node) {
057: if (DOM_LEVEL_3) {
058: return (XmlNode) node.getUserData(USER_DATA_XMLNODE_KEY);
059: }
060: return null;
061: }
062:
063: private static void setUserData(Node node, XmlNode wrap) {
064: if (DOM_LEVEL_3) {
065: node.setUserData(USER_DATA_XMLNODE_KEY, wrap, wrap.events);
066: }
067: }
068:
069: private static XmlNode createImpl(Node node) {
070: if (node instanceof Document)
071: throw new IllegalArgumentException();
072: XmlNode rv = null;
073: if (getUserData(node) == null) {
074: rv = new XmlNode();
075: rv.dom = node;
076: setUserData(node, rv);
077: } else {
078: rv = getUserData(node);
079: }
080: return rv;
081: }
082:
083: static XmlNode newElementWithText(XmlProcessor processor,
084: XmlNode reference, XmlNode.QName qname, String value) {
085: if (reference instanceof org.w3c.dom.Document)
086: throw new IllegalArgumentException(
087: "Cannot use Document node as reference");
088: Document document = null;
089: if (reference != null) {
090: document = reference.dom.getOwnerDocument();
091: } else {
092: document = processor.newDocument();
093: }
094: Node referenceDom = (reference != null) ? reference.dom : null;
095: Element e = document.createElementNS(qname.getUri(), qname
096: .qualify(referenceDom));
097: if (value != null) {
098: e.appendChild(document.createTextNode(value));
099: }
100: return XmlNode.createImpl(e);
101: }
102:
103: static XmlNode createText(XmlProcessor processor, String value) {
104: return createImpl(processor.newDocument().createTextNode(value));
105: }
106:
107: static XmlNode createElementFromNode(Node node) {
108: if (node instanceof Document)
109: node = ((Document) node).getDocumentElement();
110: return createImpl(node);
111: }
112:
113: static XmlNode createElement(XmlProcessor processor,
114: String namespaceUri, String xml)
115: throws org.xml.sax.SAXException {
116: return createImpl(processor.toXml(namespaceUri, xml));
117: }
118:
119: static XmlNode createEmpty(XmlProcessor processor) {
120: return createText(processor, "");
121: }
122:
123: private static XmlNode copy(XmlNode other) {
124: return createImpl(other.dom.cloneNode(true));
125: }
126:
127: private static final long serialVersionUID = 1L;
128:
129: private UserDataHandler events = new UserDataHandler() {
130: public void handle(short operation, String key, Object data,
131: Node src, Node dest) {
132: }
133: };
134:
135: private Node dom;
136:
137: private XML xml;
138:
139: private XmlNode() {
140: }
141:
142: String debug() {
143: XmlProcessor raw = new XmlProcessor();
144: raw.setIgnoreComments(false);
145: raw.setIgnoreProcessingInstructions(false);
146: raw.setIgnoreWhitespace(false);
147: raw.setPrettyPrinting(false);
148: return raw.ecmaToXmlString(this .dom);
149: }
150:
151: public String toString() {
152: return "XmlNode: type=" + dom.getNodeType() + " dom="
153: + dom.toString();
154: }
155:
156: XML getXml() {
157: return xml;
158: }
159:
160: void setXml(XML xml) {
161: this .xml = xml;
162: }
163:
164: int getChildCount() {
165: return this .dom.getChildNodes().getLength();
166: }
167:
168: XmlNode parent() {
169: Node domParent = dom.getParentNode();
170: if (domParent instanceof Document)
171: return null;
172: if (domParent == null)
173: return null;
174: return createImpl(domParent);
175: }
176:
177: int getChildIndex() {
178: if (this .isAttributeType())
179: return -1;
180: if (parent() == null)
181: return -1;
182: org.w3c.dom.NodeList siblings = this .dom.getParentNode()
183: .getChildNodes();
184: for (int i = 0; i < siblings.getLength(); i++) {
185: if (siblings.item(i) == dom) {
186: return i;
187: }
188: }
189: // Either the parent is -1 or one of the this node's parent's children is this node.
190: throw new RuntimeException("Unreachable.");
191: }
192:
193: void removeChild(int index) {
194: this .dom.removeChild(this .dom.getChildNodes().item(index));
195: }
196:
197: String toXmlString(XmlProcessor processor) {
198: return processor.ecmaToXmlString(this .dom);
199: }
200:
201: String ecmaValue() {
202: // TODO See ECMA 357 Section 9.1
203: if (isTextType()) {
204: return ((org.w3c.dom.Text) dom).getData();
205: } else if (isAttributeType()) {
206: return ((org.w3c.dom.Attr) dom).getValue();
207: } else if (isProcessingInstructionType()) {
208: return ((org.w3c.dom.ProcessingInstruction) dom).getData();
209: } else if (isCommentType()) {
210: return ((org.w3c.dom.Comment) dom).getNodeValue();
211: } else if (isElementType()) {
212: throw new RuntimeException(
213: "Unimplemented ecmaValue() for elements.");
214: } else {
215: throw new RuntimeException("Unimplemented for node " + dom);
216: }
217: }
218:
219: void deleteMe() {
220: if (dom instanceof Attr) {
221: Attr attr = (Attr) this .dom;
222: attr.getOwnerElement().getAttributes().removeNamedItemNS(
223: attr.getNamespaceURI(), attr.getLocalName());
224: } else {
225: if (this .dom.getParentNode() != null) {
226: this .dom.getParentNode().removeChild(this .dom);
227: } else {
228: // This case can be exercised at least when executing the regression
229: // tests under https://bugzilla.mozilla.org/show_bug.cgi?id=354145
230: }
231: }
232: }
233:
234: void normalize() {
235: this .dom.normalize();
236: }
237:
238: void insertChildAt(int index, XmlNode node) {
239: Node parent = this .dom;
240: Node child = parent.getOwnerDocument().importNode(node.dom,
241: true);
242: if (parent.getChildNodes().getLength() < index) {
243: // TODO Check ECMA for what happens here
244: throw new IllegalArgumentException("index=" + index
245: + " length=" + parent.getChildNodes().getLength());
246: }
247: if (parent.getChildNodes().getLength() == index) {
248: parent.appendChild(child);
249: } else {
250: parent.insertBefore(child, parent.getChildNodes().item(
251: index));
252: }
253: }
254:
255: void insertChildrenAt(int index, XmlNode[] nodes) {
256: for (int i = 0; i < nodes.length; i++) {
257: insertChildAt(index + i, nodes[i]);
258: }
259: }
260:
261: XmlNode getChild(int index) {
262: Node child = dom.getChildNodes().item(index);
263: return createImpl(child);
264: }
265:
266: // Helper method for XML.hasSimpleContent()
267: boolean hasChildElement() {
268: org.w3c.dom.NodeList nodes = this .dom.getChildNodes();
269: for (int i = 0; i < nodes.getLength(); i++) {
270: if (nodes.item(i).getNodeType() == org.w3c.dom.Node.ELEMENT_NODE)
271: return true;
272: }
273: return false;
274: }
275:
276: boolean isSameNode(XmlNode other) {
277: // TODO May need to be changed if we allow XmlNode to refer to several Node objects
278: return this .dom == other.dom;
279: }
280:
281: private String toUri(String ns) {
282: return (ns == null) ? "" : ns;
283: }
284:
285: private void addNamespaces(Namespaces rv, Element element) {
286: if (element == null)
287: throw new RuntimeException("element must not be null");
288: String myDefaultNamespace = toUri(element
289: .lookupNamespaceURI(null));
290: String parentDefaultNamespace = "";
291: if (element.getParentNode() != null) {
292: parentDefaultNamespace = toUri(element.getParentNode()
293: .lookupNamespaceURI(null));
294: }
295: if (!myDefaultNamespace.equals(parentDefaultNamespace)
296: || !(element.getParentNode() instanceof Element)) {
297: rv.declare(Namespace.create("", myDefaultNamespace));
298: }
299: NamedNodeMap attributes = element.getAttributes();
300: for (int i = 0; i < attributes.getLength(); i++) {
301: Attr attr = (Attr) attributes.item(i);
302: if (attr.getPrefix() != null
303: && attr.getPrefix().equals("xmlns")) {
304: rv.declare(Namespace.create(attr.getLocalName(), attr
305: .getValue()));
306: }
307: }
308: }
309:
310: private Namespaces getAllNamespaces() {
311: Namespaces rv = new Namespaces();
312:
313: Node target = this .dom;
314: if (target instanceof Attr) {
315: target = ((Attr) target).getOwnerElement();
316: }
317: while (target != null) {
318: if (target instanceof Element) {
319: addNamespaces(rv, (Element) target);
320: }
321: target = target.getParentNode();
322: }
323: // Fallback in case no namespace was declared
324: rv.declare(Namespace.create("", ""));
325: return rv;
326: }
327:
328: Namespace[] getInScopeNamespaces() {
329: Namespaces rv = getAllNamespaces();
330: return rv.getNamespaces();
331: }
332:
333: Namespace[] getNamespaceDeclarations() {
334: // ECMA357 13.4.4.24
335: if (this .dom instanceof Element) {
336: Namespaces rv = new Namespaces();
337: addNamespaces(rv, (Element) this .dom);
338: return rv.getNamespaces();
339: } else {
340: return new Namespace[0];
341: }
342: }
343:
344: Namespace getNamespaceDeclaration(String prefix) {
345: if (prefix.equals("") && dom instanceof Attr) {
346: // Default namespaces do not apply to attributes; see XML Namespaces section 5.2
347: return Namespace.create("", "");
348: }
349: Namespaces rv = getAllNamespaces();
350: return rv.getNamespace(prefix);
351: }
352:
353: Namespace getNamespaceDeclaration() {
354: if (dom.getPrefix() == null)
355: return getNamespaceDeclaration("");
356: return getNamespaceDeclaration(dom.getPrefix());
357: }
358:
359: private static class Namespaces {
360: private HashMap map = new HashMap();
361: private HashMap uriToPrefix = new HashMap();
362:
363: Namespaces() {
364: }
365:
366: void declare(Namespace n) {
367: if (map.get(n.prefix) == null) {
368: map.put(n.prefix, n.uri);
369: }
370: // TODO I think this is analogous to the other way, but have not really thought it through ... should local scope
371: // matter more than outer scope?
372: if (uriToPrefix.get(n.uri) == null) {
373: uriToPrefix.put(n.uri, n.prefix);
374: }
375: }
376:
377: Namespace getNamespaceByUri(String uri) {
378: if (uriToPrefix.get(uri) == null)
379: return null;
380: return Namespace.create(uri, (String) uriToPrefix.get(uri));
381: }
382:
383: Namespace getNamespace(String prefix) {
384: if (map.get(prefix) == null)
385: return null;
386: return Namespace.create(prefix, (String) map.get(prefix));
387: }
388:
389: Namespace[] getNamespaces() {
390: Iterator i = map.keySet().iterator();
391: ArrayList rv = new ArrayList();
392: while (i.hasNext()) {
393: String prefix = (String) i.next();
394: String uri = (String) map.get(prefix);
395: Namespace n = Namespace.create(prefix, uri);
396: if (!n.isEmpty()) {
397: rv.add(n);
398: }
399: }
400: return (Namespace[]) rv.toArray(new Namespace[0]);
401: }
402: }
403:
404: final XmlNode copy() {
405: return copy(this );
406: }
407:
408: // Returns whether this node is capable of being a parent
409: final boolean isParentType() {
410: return isElementType();
411: }
412:
413: final boolean isTextType() {
414: return dom.getNodeType() == Node.TEXT_NODE
415: || dom.getNodeType() == Node.CDATA_SECTION_NODE;
416: }
417:
418: final boolean isAttributeType() {
419: return dom.getNodeType() == Node.ATTRIBUTE_NODE;
420: }
421:
422: final boolean isProcessingInstructionType() {
423: return dom.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE;
424: }
425:
426: final boolean isCommentType() {
427: return dom.getNodeType() == Node.COMMENT_NODE;
428: }
429:
430: final boolean isElementType() {
431: return dom.getNodeType() == Node.ELEMENT_NODE;
432: }
433:
434: final void renameNode(QName qname) {
435: this .dom = dom.getOwnerDocument().renameNode(dom,
436: qname.getUri(), qname.qualify(dom));
437: }
438:
439: void invalidateNamespacePrefix() {
440: if (!(dom instanceof Element))
441: throw new IllegalStateException();
442: String prefix = this .dom.getPrefix();
443: QName after = QName.create(this .dom.getNamespaceURI(), this .dom
444: .getLocalName(), null);
445: renameNode(after);
446: NamedNodeMap attrs = this .dom.getAttributes();
447: for (int i = 0; i < attrs.getLength(); i++) {
448: if (attrs.item(i).getPrefix().equals(prefix)) {
449: createImpl(attrs.item(i)).renameNode(
450: QName.create(attrs.item(i).getNamespaceURI(),
451: attrs.item(i).getLocalName(), null));
452: }
453: }
454: }
455:
456: private void declareNamespace(Element e, String prefix, String uri) {
457: if (prefix.length() > 0) {
458: e.setAttributeNS(XML_NAMESPACES_NAMESPACE_URI, "xmlns:"
459: + prefix, uri);
460: } else {
461: e.setAttribute("xmlns", uri);
462: }
463: }
464:
465: void declareNamespace(String prefix, String uri) {
466: if (!(dom instanceof Element))
467: throw new IllegalStateException();
468: if (dom.lookupNamespaceURI(uri) != null
469: && dom.lookupNamespaceURI(uri).equals(prefix)) {
470: // do nothing
471: } else {
472: Element e = (Element) dom;
473: declareNamespace(e, prefix, uri);
474: }
475: }
476:
477: private Namespace getDefaultNamespace() {
478: String prefix = "";
479: String uri = (dom.lookupNamespaceURI(null) == null) ? "" : dom
480: .lookupNamespaceURI(null);
481: return Namespace.create(prefix, uri);
482: }
483:
484: private String getExistingPrefixFor(Namespace namespace) {
485: if (getDefaultNamespace().getUri().equals(namespace.getUri())) {
486: return "";
487: }
488: return dom.lookupPrefix(namespace.getUri());
489: }
490:
491: private Namespace getNodeNamespace() {
492: String uri = dom.getNamespaceURI();
493: String prefix = dom.getPrefix();
494: if (uri == null)
495: uri = "";
496: if (prefix == null)
497: prefix = "";
498: return Namespace.create(prefix, uri);
499: }
500:
501: Namespace getNamespace() {
502: return getNodeNamespace();
503: }
504:
505: void removeNamespace(Namespace namespace) {
506: Namespace current = getNodeNamespace();
507:
508: // Do not remove in-use namespace
509: if (namespace.is(current))
510: return;
511: NamedNodeMap attrs = this .dom.getAttributes();
512: for (int i = 0; i < attrs.getLength(); i++) {
513: XmlNode attr = XmlNode.createImpl(attrs.item(i));
514: if (namespace.is(attr.getNodeNamespace()))
515: return;
516: }
517:
518: // TODO I must confess I am not sure I understand the spec fully. See ECMA357 13.4.4.31
519: String existingPrefix = getExistingPrefixFor(namespace);
520: if (existingPrefix != null) {
521: if (namespace.isUnspecifiedPrefix()) {
522: // we should remove any namespace with this URI from scope; we do this by declaring a namespace with the same
523: // prefix as the existing prefix and setting its URI to the default namespace
524: declareNamespace(existingPrefix, getDefaultNamespace()
525: .getUri());
526: } else {
527: if (existingPrefix.equals(namespace.getPrefix())) {
528: declareNamespace(existingPrefix,
529: getDefaultNamespace().getUri());
530: }
531: }
532: } else {
533: // the argument namespace is not declared in this scope, so do nothing.
534: }
535: }
536:
537: private void setProcessingInstructionName(String localName) {
538: org.w3c.dom.ProcessingInstruction pi = (ProcessingInstruction) this .dom;
539: // We cannot set the node name; Document.renameNode() only supports elements and attributes. So we replace it
540: pi.getParentNode().replaceChild(
541: pi,
542: pi.getOwnerDocument().createProcessingInstruction(
543: localName, pi.getData()));
544: }
545:
546: final void setLocalName(String localName) {
547: if (dom instanceof ProcessingInstruction) {
548: setProcessingInstructionName(localName);
549: } else {
550: String prefix = dom.getPrefix();
551: if (prefix == null)
552: prefix = "";
553: this .dom = dom.getOwnerDocument().renameNode(dom,
554: dom.getNamespaceURI(),
555: QName.qualify(prefix, localName));
556: }
557: }
558:
559: final QName getQname() {
560: String uri = (dom.getNamespaceURI()) == null ? "" : dom
561: .getNamespaceURI();
562: String prefix = (dom.getPrefix() == null) ? "" : dom
563: .getPrefix();
564: return QName.create(uri, dom.getLocalName(), prefix);
565: }
566:
567: void addMatchingChildren(XMLList result, XmlNode.Filter filter) {
568: Node node = this .dom;
569: NodeList children = node.getChildNodes();
570: for (int i = 0; i < children.getLength(); i++) {
571: Node childnode = children.item(i);
572: XmlNode child = XmlNode.createImpl(childnode);
573: if (filter.accept(childnode)) {
574: result.addToList(child);
575: }
576: }
577: }
578:
579: XmlNode[] getMatchingChildren(Filter filter) {
580: ArrayList rv = new ArrayList();
581: NodeList nodes = this .dom.getChildNodes();
582: for (int i = 0; i < nodes.getLength(); i++) {
583: Node node = nodes.item(i);
584: if (filter.accept(node)) {
585: rv.add(createImpl(node));
586: }
587: }
588: return (XmlNode[]) rv.toArray(new XmlNode[0]);
589: }
590:
591: XmlNode[] getAttributes() {
592: NamedNodeMap attrs = this .dom.getAttributes();
593: // TODO Or could make callers handle null?
594: if (attrs == null)
595: throw new IllegalStateException("Must be element.");
596: XmlNode[] rv = new XmlNode[attrs.getLength()];
597: for (int i = 0; i < attrs.getLength(); i++) {
598: rv[i] = createImpl(attrs.item(i));
599: }
600: return rv;
601: }
602:
603: String getAttributeValue() {
604: return ((Attr) dom).getValue();
605: }
606:
607: void setAttribute(QName name, String value) {
608: if (!(dom instanceof Element))
609: throw new IllegalStateException(
610: "Can only set attribute on elements.");
611: name.setAttribute((Element) dom, value);
612: }
613:
614: void replaceWith(XmlNode other) {
615: Node replacement = other.dom;
616: if (replacement.getOwnerDocument() != this .dom
617: .getOwnerDocument()) {
618: replacement = this .dom.getOwnerDocument().importNode(
619: replacement, true);
620: }
621: this .dom.getParentNode().replaceChild(replacement, this .dom);
622: }
623:
624: String ecmaToXMLString(XmlProcessor processor) {
625: if (this .isElementType()) {
626: Element copy = (Element) this .dom.cloneNode(true);
627: Namespace[] inScope = this .getInScopeNamespaces();
628: for (int i = 0; i < inScope.length; i++) {
629: declareNamespace(copy, inScope[i].getPrefix(),
630: inScope[i].getUri());
631: }
632: return processor.ecmaToXmlString(copy);
633: } else {
634: return processor.ecmaToXmlString(dom);
635: }
636: }
637:
638: static class Namespace {
639: static Namespace create(String prefix, String uri) {
640: if (prefix == null)
641: throw new IllegalArgumentException(
642: "Empty string represents default namespace prefix");
643: if (uri == null)
644: throw new IllegalArgumentException(
645: "Namespace may not lack a URI");
646: Namespace rv = new Namespace();
647: rv.prefix = prefix;
648: rv.uri = uri;
649: return rv;
650: }
651:
652: static Namespace create(String uri) {
653: Namespace rv = new Namespace();
654: rv.uri = uri;
655: return rv;
656: }
657:
658: static final Namespace GLOBAL = create("", "");
659:
660: private String prefix;
661: private String uri;
662:
663: private Namespace() {
664: }
665:
666: public String toString() {
667: if (prefix == null)
668: return "XmlNode.Namespace [" + uri + "]";
669: return "XmlNode.Namespace [" + prefix + "{" + uri + "}]";
670: }
671:
672: boolean isUnspecifiedPrefix() {
673: return prefix == null;
674: }
675:
676: boolean is(Namespace other) {
677: return this .prefix != null && other.prefix != null
678: && this .prefix.equals(other.prefix)
679: && this .uri.equals(other.uri);
680: }
681:
682: boolean isEmpty() {
683: return prefix != null && prefix.equals("")
684: && uri.equals("");
685: }
686:
687: boolean isDefault() {
688: return prefix != null && prefix.equals("");
689: }
690:
691: boolean isGlobal() {
692: return uri != null && uri.equals("");
693: }
694:
695: // Called by QName
696: // TODO Move functionality from QName lookupPrefix to here
697: private void setPrefix(String prefix) {
698: if (prefix == null)
699: throw new IllegalArgumentException();
700: this .prefix = prefix;
701: }
702:
703: String getPrefix() {
704: return prefix;
705: }
706:
707: String getUri() {
708: return uri;
709: }
710: }
711:
712: // TODO Where is this class used? No longer using it in QName implementation
713: static class QName {
714: static QName create(Namespace namespace, String localName) {
715: // A null namespace indicates a wild-card match for any namespace
716: // A null localName indicates "*" from the point of view of ECMA357
717: if (localName != null && localName.equals("*"))
718: throw new RuntimeException("* is not valid localName");
719: QName rv = new QName();
720: rv.namespace = namespace;
721: rv.localName = localName;
722: return rv;
723: }
724:
725: /** @deprecated */
726: static QName create(String uri, String localName, String prefix) {
727: return create(Namespace.create(prefix, uri), localName);
728: }
729:
730: static String qualify(String prefix, String localName) {
731: if (prefix == null)
732: throw new IllegalArgumentException(
733: "prefix must not be null");
734: if (prefix.length() > 0)
735: return prefix + ":" + localName;
736: return localName;
737: }
738:
739: private Namespace namespace;
740: private String localName;
741:
742: private QName() {
743: }
744:
745: public String toString() {
746: return "XmlNode.QName [" + localName + "," + namespace
747: + "]";
748: }
749:
750: private boolean equals(String one, String two) {
751: if (one == null && two == null)
752: return true;
753: if (one == null || two == null)
754: return false;
755: return one.equals(two);
756: }
757:
758: private boolean namespacesEqual(Namespace one, Namespace two) {
759: if (one == null && two == null)
760: return true;
761: if (one == null || two == null)
762: return false;
763: return equals(one.getUri(), two.getUri());
764: }
765:
766: final boolean isEqualTo(QName other) {
767: if (!namespacesEqual(this .namespace, other.namespace))
768: return false;
769: if (!equals(this .localName, other.localName))
770: return false;
771: return true;
772: }
773:
774: void lookupPrefix(org.w3c.dom.Node node) {
775: if (node == null)
776: throw new IllegalArgumentException(
777: "node must not be null");
778: String prefix = node.lookupPrefix(namespace.getUri());
779: if (prefix == null) {
780: // check to see if we match the default namespace
781: String defaultNamespace = node.lookupNamespaceURI(null);
782: if (defaultNamespace == null)
783: defaultNamespace = "";
784: String nodeNamespace = namespace.getUri();
785: if (nodeNamespace.equals(defaultNamespace)) {
786: prefix = "";
787: }
788: }
789: int i = 0;
790: while (prefix == null) {
791: String generatedPrefix = "e4x_" + i++;
792: String generatedUri = node
793: .lookupNamespaceURI(generatedPrefix);
794: if (generatedUri == null) {
795: prefix = generatedPrefix;
796: org.w3c.dom.Node top = node;
797: while (top.getParentNode() != null
798: && top.getParentNode() instanceof org.w3c.dom.Element) {
799: top = top.getParentNode();
800: }
801: ((org.w3c.dom.Element) top).setAttributeNS(
802: "http://www.w3.org/2000/xmlns/", "xmlns:"
803: + prefix, namespace.getUri());
804: }
805: }
806: namespace.setPrefix(prefix);
807: }
808:
809: String qualify(org.w3c.dom.Node node) {
810: if (namespace.getPrefix() == null) {
811: if (node != null) {
812: lookupPrefix(node);
813: } else {
814: if (namespace.getUri().equals("")) {
815: namespace.setPrefix("");
816: } else {
817: // TODO I am not sure this is right, but if we are creating a standalone node, I think we can set the
818: // default namespace on the node itself and not worry about setting a prefix for that namespace.
819: namespace.setPrefix("");
820: }
821: }
822: }
823: return qualify(namespace.getPrefix(), localName);
824: }
825:
826: void setAttribute(org.w3c.dom.Element element, String value) {
827: if (namespace.getPrefix() == null)
828: lookupPrefix(element);
829: element.setAttributeNS(namespace.getUri(), qualify(
830: namespace.getPrefix(), localName), value);
831: }
832:
833: /** @deprecated Use getNamespace() */
834: String getUri() {
835: return namespace.getUri();
836: }
837:
838: /** @deprecated Use getNamespace() */
839: String getPrefix() {
840: return namespace.getPrefix();
841: }
842:
843: Namespace getNamespace() {
844: return namespace;
845: }
846:
847: String getLocalName() {
848: return localName;
849: }
850: }
851:
852: static class List {
853: private java.util.Vector v;
854:
855: List() {
856: v = new java.util.Vector();
857: }
858:
859: private void _add(XmlNode n) {
860: v.add(n);
861: }
862:
863: XmlNode item(int index) {
864: return (XmlNode) (v.get(index));
865: }
866:
867: void remove(int index) {
868: v.remove(index);
869: }
870:
871: void add(List other) {
872: for (int i = 0; i < other.length(); i++) {
873: _add(other.item(i));
874: }
875: }
876:
877: void add(List from, int startInclusive, int endExclusive) {
878: for (int i = startInclusive; i < endExclusive; i++) {
879: _add(from.item(i));
880: }
881: }
882:
883: void add(XmlNode node) {
884: _add(node);
885: }
886:
887: /** @deprecated */
888: void add(XML xml) {
889: _add(xml.getAnnotation());
890: }
891:
892: /** @deprecated */
893: void addToList(Object toAdd) {
894: if (toAdd instanceof Undefined) {
895: // Missing argument do nothing...
896: return;
897: }
898:
899: if (toAdd instanceof XMLList) {
900: XMLList xmlSrc = (XMLList) toAdd;
901: for (int i = 0; i < xmlSrc.length(); i++) {
902: this ._add((xmlSrc.item(i)).getAnnotation());
903: }
904: } else if (toAdd instanceof XML) {
905: this ._add(((XML) (toAdd)).getAnnotation());
906: } else if (toAdd instanceof XmlNode) {
907: this ._add((XmlNode) toAdd);
908: }
909: }
910:
911: int length() {
912: return v.size();
913: }
914: }
915:
916: static abstract class Filter {
917: static final Filter COMMENT = new Filter() {
918: boolean accept(Node node) {
919: return node.getNodeType() == Node.COMMENT_NODE;
920: }
921: };
922: static final Filter TEXT = new Filter() {
923: boolean accept(Node node) {
924: return node.getNodeType() == Node.TEXT_NODE;
925: }
926: };
927:
928: static Filter PROCESSING_INSTRUCTION(final XMLName name) {
929: return new Filter() {
930: boolean accept(Node node) {
931: if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
932: ProcessingInstruction pi = (ProcessingInstruction) node;
933: return name.matchesLocalName(pi.getTarget());
934: }
935: return false;
936: }
937: };
938: }
939:
940: static Filter ELEMENT = new Filter() {
941: boolean accept(Node node) {
942: return node.getNodeType() == Node.ELEMENT_NODE;
943: }
944: };
945: static Filter TRUE = new Filter() {
946: boolean accept(Node node) {
947: return true;
948: }
949: };
950:
951: abstract boolean accept(Node node);
952: }
953:
954: // Support experimental Java interface
955: org.w3c.dom.Node toDomNode() {
956: return this.dom;
957: }
958: }
|