0001: /* Copyright 2002-2005 Elliotte Rusty Harold
0002:
0003: This library is free software; you can redistribute it and/or modify
0004: it under the terms of version 2.1 of the GNU Lesser General Public
0005: License as published by the Free Software Foundation.
0006:
0007: This library is distributed in the hope that it will be useful,
0008: but WITHOUT ANY WARRANTY; without even the implied warranty of
0009: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0010: GNU Lesser General Public License for more details.
0011:
0012: You should have received a copy of the GNU Lesser General Public
0013: License along with this library; if not, write to the
0014: Free Software Foundation, Inc., 59 Temple Place, Suite 330,
0015: Boston, MA 02111-1307 USA
0016:
0017: You can contact Elliotte Rusty Harold by sending e-mail to
0018: elharo@metalab.unc.edu. Please include the word "XOM" in the
0019: subject line. The XOM home page is located at http://www.xom.nu/
0020: */
0021:
0022: package nu.xom;
0023:
0024: import java.util.HashMap;
0025: import java.util.HashSet;
0026: import java.util.Iterator;
0027: import java.util.Map;
0028: import java.util.NoSuchElementException; // ???? Why do I still need these imports? Could
0029: // I get rid of them? In particular could I get rid of sorting requirement on sets?
0030: import java.util.Set;
0031: import java.util.SortedSet;
0032: import java.util.TreeSet;
0033:
0034: /**
0035: * <p>
0036: * This class represents an XML element. Each
0037: * element has the following properties:
0038: * </p>
0039: *
0040: * <ul>
0041: * <li>Local name</li>
0042: * <li>Prefix (which may be null or the empty string) </li>
0043: * <li>Namespace URI (which may be null or the empty string) </li>
0044: * <li>A list of attributes</li>
0045: * <li>A list of namespace declarations for this element
0046: * (not including those inherited from its parent)</li>
0047: * <li>A list of child nodes</li>
0048: * </ul>
0049: *
0050: * @author Elliotte Rusty Harold
0051: * @version 1.1b3
0052: *
0053: */
0054: public class Element extends ParentNode {
0055:
0056: private String localName;
0057: private String prefix;
0058: private String URI;
0059:
0060: private Attribute[] attributes = null;
0061: private int numAttributes = 0;
0062: private Namespaces namespaces = null;
0063:
0064: /**
0065: * <p>
0066: * Creates a new element in no namespace.
0067: * </p>
0068: *
0069: * @param name the name of the element
0070: *
0071: * @throws IllegalNameException if <code>name</code>
0072: * is not a legal XML 1.0 non-colonized name
0073: */
0074: public Element(String name) {
0075: this (name, "");
0076: }
0077:
0078: /**
0079: * <p>
0080: * Creates a new element in a namespace.
0081: * </p>
0082: *
0083: * @param name the qualified name of the element
0084: * @param uri the namespace URI of the element
0085: *
0086: * @throws IllegalNameException if <code>name</code>
0087: * is not a legal XML 1.0 name
0088: * @throws NamespaceConflictException if <code>name</code>'s prefix
0089: * cannot be used with <code>uri</code>
0090: * @throws MalformedURIException if <code>uri</code>
0091: * is not an RFC 3986 absolute URI reference
0092: */
0093: public Element(String name, String uri) {
0094:
0095: // The shadowing is important here.
0096: // I don't want to set the prefix field just yet.
0097: String prefix = "";
0098: String localName = name;
0099: int colon = name.indexOf(':');
0100: if (colon > 0) {
0101: prefix = name.substring(0, colon);
0102: localName = name.substring(colon + 1);
0103: }
0104:
0105: // The order of these next two calls
0106: // matters a great deal.
0107: _setNamespacePrefix(prefix);
0108: _setNamespaceURI(uri);
0109: try {
0110: _setLocalName(localName);
0111: } catch (IllegalNameException ex) {
0112: ex.setData(name);
0113: throw ex;
0114: }
0115:
0116: }
0117:
0118: private Element() {
0119: }
0120:
0121: static Element build(String name, String uri, String localName) {
0122:
0123: Element result = new Element();
0124: String prefix = "";
0125: int colon = name.indexOf(':');
0126: if (colon >= 0) {
0127: prefix = name.substring(0, colon);
0128: }
0129: result.prefix = prefix;
0130: result.localName = localName;
0131: // We do need to verify the URI here because parsers are
0132: // allowing relative URIs which XOM forbids, for reasons
0133: // of canonical XML if nothing else. But we only have to verify
0134: // that it's an absolute base URI. I don't have to verify
0135: // no conflicts.
0136: if (!"".equals(uri))
0137: Verifier.checkAbsoluteURIReference(uri);
0138: result.URI = uri;
0139: return result;
0140:
0141: }
0142:
0143: /**
0144: * <p>
0145: * Creates a deep copy of an element.
0146: * The copy is disconnected from the tree, and does not
0147: * have a parent.
0148: * </p>
0149: *
0150: * @param element the element to copy
0151: *
0152: */
0153: public Element(Element element) {
0154:
0155: this .prefix = element.prefix;
0156: this .localName = element.localName;
0157: this .URI = element.URI;
0158:
0159: // Attach additional namespaces
0160: if (element.namespaces != null) {
0161: this .namespaces = element.namespaces.copy();
0162: }
0163:
0164: // Attach clones of attributes
0165: if (element.attributes != null) {
0166: this .attributes = element.copyAttributes(this );
0167: this .numAttributes = element.numAttributes;
0168: }
0169:
0170: this .actualBaseURI = element.findActualBaseURI();
0171:
0172: copyChildren(element, this );
0173:
0174: }
0175:
0176: private Attribute[] copyAttributes(Element newParent) {
0177:
0178: Attribute[] copy = new Attribute[numAttributes];
0179: for (int i = 0; i < numAttributes; i++) {
0180: copy[i] = (Attribute) attributes[i].copy();
0181: copy[i].setParent(newParent);
0182: }
0183: return copy;
0184:
0185: }
0186:
0187: private static Element copyTag(final Element source) {
0188:
0189: Element result = source.shallowCopy();
0190:
0191: // Attach additional namespaces
0192: if (source.namespaces != null) {
0193: result.namespaces = source.namespaces.copy();
0194: }
0195:
0196: // Attach clones of attributes
0197: if (source.attributes != null) {
0198: result.attributes = source.copyAttributes(result);
0199: result.numAttributes = source.numAttributes;
0200: }
0201:
0202: result.actualBaseURI = source.findActualBaseURI();
0203:
0204: return result;
0205:
0206: }
0207:
0208: private static void copyChildren(final Element sourceElement,
0209: Element resultElement) {
0210:
0211: if (sourceElement.getChildCount() == 0)
0212: return;
0213: ParentNode resultParent = resultElement;
0214: Node sourceCurrent = sourceElement;
0215: int index = 0;
0216: int[] indexes = new int[10];
0217: int top = 0;
0218: indexes[0] = 0;
0219:
0220: // true if processing the element for the 2nd time;
0221: // i.e. the element's end-tag
0222: boolean endTag = false;
0223:
0224: while (true) {
0225: if (!endTag && sourceCurrent.getChildCount() > 0) {
0226: sourceCurrent = sourceCurrent.getChild(0);
0227: index = 0;
0228: top++;
0229: indexes = grow(indexes, top);
0230: indexes[top] = 0;
0231: } else {
0232: endTag = false;
0233: ParentNode sourceParent = sourceCurrent.getParent();
0234: if (sourceParent.getChildCount() - 1 == index) {
0235: sourceCurrent = sourceParent;
0236: top--;
0237: if (sourceCurrent == sourceElement)
0238: break;
0239: // switch parent up
0240: resultParent = (Element) resultParent.getParent();
0241: index = indexes[top];
0242: endTag = true;
0243: continue;
0244: } else {
0245: index++;
0246: indexes[top] = index;
0247: sourceCurrent = sourceParent.getChild(index);
0248: }
0249: }
0250:
0251: if (sourceCurrent.isElement()) {
0252: Element child = copyTag((Element) sourceCurrent);
0253: resultParent.appendChild(child);
0254: if (sourceCurrent.getChildCount() > 0) {
0255: resultParent = child;
0256: }
0257: } else {
0258: Node child = sourceCurrent.copy();
0259: resultParent.appendChild(child);
0260: }
0261:
0262: }
0263:
0264: }
0265:
0266: private static int[] grow(int[] indexes, int top) {
0267:
0268: if (top < indexes.length)
0269: return indexes;
0270: int[] result = new int[indexes.length * 2];
0271: System.arraycopy(indexes, 0, result, 0, indexes.length);
0272: return result;
0273:
0274: }
0275:
0276: /**
0277: * <p>
0278: * Returns a list of the child elements of
0279: * this element with the specified name in no namespace.
0280: * The elements returned are in document order.
0281: * </p>
0282: *
0283: * @param name the name of the elements included in the list
0284: *
0285: * @return a comatose list containing the child elements of this
0286: * element with the specified name
0287: */
0288: public final Elements getChildElements(String name) {
0289: return getChildElements(name, "");
0290: }
0291:
0292: /**
0293: * <p>
0294: * Returns a list of the immediate child elements of this
0295: * element with the specified local name and namespace URI.
0296: * Passing the empty string or null as the local name
0297: * returns all elements in the specified namespace.
0298: * Passing null or the empty string as the namespace URI
0299: * returns elements with the specified name in no namespace.
0300: * The elements returned are in document order.
0301: * </p>
0302: *
0303: * @param localName the name of the elements included in the list
0304: * @param namespaceURI the namespace URI of the elements included
0305: * in the list
0306: *
0307: * @return a comatose list containing the child
0308: * elements of this element with the specified
0309: * name in the specified namespace
0310: */
0311: public final Elements getChildElements(String localName,
0312: String namespaceURI) {
0313:
0314: if (namespaceURI == null)
0315: namespaceURI = "";
0316: if (localName == null)
0317: localName = "";
0318:
0319: Elements elements = new Elements();
0320: for (int i = 0; i < getChildCount(); i++) {
0321: Node child = getChild(i);
0322: if (child.isElement()) {
0323: Element element = (Element) child;
0324: if ((localName.equals(element.getLocalName()) || localName
0325: .length() == 0)
0326: && namespaceURI.equals(element
0327: .getNamespaceURI())) {
0328: elements.add(element);
0329: }
0330: }
0331: }
0332: return elements;
0333:
0334: }
0335:
0336: /**
0337: * <p>
0338: * Returns a list of all the child elements
0339: * of this element in document order.
0340: * </p>
0341: *
0342: * @return a comatose list containing all
0343: * child elements of this element
0344: */
0345: public final Elements getChildElements() {
0346:
0347: Elements elements = new Elements();
0348: for (int i = 0; i < getChildCount(); i++) {
0349: Node child = getChild(i);
0350: if (child.isElement()) {
0351: Element element = (Element) child;
0352: elements.add(element);
0353: }
0354: }
0355: return elements;
0356:
0357: }
0358:
0359: /**
0360: * <p>
0361: * Returns the first child
0362: * element with the specified name in no namespace.
0363: * If there is no such element, it returns null.
0364: * </p>
0365: *
0366: * @param name the name of the element to return
0367: *
0368: * @return the first child element with the specified local name
0369: * in no namespace or null if there is no such element
0370: */
0371: public final Element getFirstChildElement(String name) {
0372: return getFirstChildElement(name, "");
0373: }
0374:
0375: /**
0376: * <p>
0377: * Returns the first child
0378: * element with the specified local name and namespace URI.
0379: * If there is no such element, it returns null.
0380: * </p>
0381: *
0382: * @param localName the local name of the element to return
0383: * @param namespaceURI the namespace URI of the element to return
0384: *
0385: * @return the first child with the specified local name in the
0386: * specified namespace, or null if there is no such element
0387: */
0388: public final Element getFirstChildElement(String localName,
0389: String namespaceURI) {
0390:
0391: for (int i = 0; i < getChildCount(); i++) {
0392: Node child = getChild(i);
0393: if (child.isElement()) {
0394: Element element = (Element) child;
0395: if (localName.equals(element.getLocalName())
0396: && namespaceURI.equals(element
0397: .getNamespaceURI())) {
0398: return element;
0399: }
0400: }
0401: }
0402: return null;
0403:
0404: }
0405:
0406: /**
0407: * <p>
0408: * Adds an attribute to this element, replacing any existing
0409: * attribute with the same local name and namespace URI.
0410: * </p>
0411: *
0412: * @param attribute the attribute to add
0413: *
0414: * @throws MultipleParentException if the attribute is already
0415: * attached to an element
0416: * @throws NamespaceConflictException if the attribute's prefix
0417: * is mapped to a different namespace URI than the same prefix
0418: * is mapped to by this element, another attribute of
0419: * this element, or an additional namespace declaration
0420: * of this element
0421: */
0422: public void addAttribute(Attribute attribute) {
0423:
0424: if (attribute.getParent() != null) {
0425: throw new MultipleParentException(
0426: "Attribute already has a parent");
0427: }
0428:
0429: // check for namespace conflicts
0430: String attPrefix = attribute.getNamespacePrefix();
0431: if (attPrefix.length() != 0 && !"xml".equals(attPrefix)) {
0432: if (prefix.equals(attribute.getNamespacePrefix())
0433: && !(getNamespaceURI().equals(attribute
0434: .getNamespaceURI()))) {
0435: throw new NamespaceConflictException("Prefix of "
0436: + attribute.getQualifiedName()
0437: + " conflicts with element prefix " + prefix);
0438: }
0439: // check for conflicts with additional namespaces
0440: if (namespaces != null) {
0441: String existing = namespaces.getURI(attribute
0442: .getNamespacePrefix());
0443: if (existing != null
0444: && !existing
0445: .equals(attribute.getNamespaceURI())) {
0446: throw new NamespaceConflictException(
0447: "Attribute prefix "
0448: + attPrefix
0449: + " conflicts with namespace declaration.");
0450: }
0451: }
0452:
0453: }
0454:
0455: if (attributes == null)
0456: attributes = new Attribute[1];
0457: checkPrefixConflict(attribute);
0458:
0459: // Is there already an attribute with this local name
0460: // and namespace? If so, remove it.
0461: Attribute oldAttribute = getAttribute(attribute.getLocalName(),
0462: attribute.getNamespaceURI());
0463: if (oldAttribute != null)
0464: remove(oldAttribute);
0465:
0466: add(attribute);
0467: attribute.setParent(this );
0468:
0469: }
0470:
0471: private void add(Attribute attribute) {
0472:
0473: if (numAttributes == attributes.length) {
0474: Attribute[] newAttributes = new Attribute[attributes.length * 2];
0475: System.arraycopy(attributes, 0, newAttributes, 0,
0476: numAttributes);
0477: this .attributes = newAttributes;
0478: }
0479: attributes[numAttributes] = attribute;
0480: numAttributes++;
0481:
0482: }
0483:
0484: private boolean remove(Attribute attribute) {
0485:
0486: int index = -1;
0487: for (int i = 0; i < attributes.length; i++) {
0488: if (attributes[i] == attribute) {
0489: index = i;
0490: break;
0491: }
0492: }
0493:
0494: if (index == -1)
0495: return false;
0496:
0497: int toCopy = numAttributes - index - 1;
0498: if (toCopy > 0) {
0499: System.arraycopy(attributes, index + 1, attributes, index,
0500: toCopy);
0501: }
0502: numAttributes--;
0503: attributes[numAttributes] = null;
0504: return true;
0505:
0506: }
0507:
0508: void fastAddAttribute(Attribute attribute) {
0509: if (attributes == null)
0510: attributes = new Attribute[1];
0511: add(attribute);
0512: attribute.setParent(this );
0513: }
0514:
0515: /**
0516: * <p>
0517: * Removes an attribute from this element.
0518: * </p>
0519: *
0520: * @param attribute the attribute to remove
0521: *
0522: * @return the attribute that was removed
0523: *
0524: * @throws NoSuchAttributeException if this element is not the
0525: * parent of attribute
0526: *
0527: */
0528: public Attribute removeAttribute(Attribute attribute) {
0529:
0530: if (attributes == null) {
0531: throw new NoSuchAttributeException(
0532: "Tried to remove attribute "
0533: + attribute.getQualifiedName()
0534: + " from non-parent element");
0535: }
0536: if (attribute == null) {
0537: throw new NullPointerException(
0538: "Tried to remove null attribute");
0539: }
0540: if (remove(attribute)) {
0541: attribute.setParent(null);
0542: return attribute;
0543: } else {
0544: throw new NoSuchAttributeException(
0545: "Tried to remove attribute "
0546: + attribute.getQualifiedName()
0547: + " from non-parent element");
0548: }
0549:
0550: }
0551:
0552: /**
0553: * <p>
0554: * Returns the attribute with the specified name in no namespace,
0555: * or null if this element does not have an attribute
0556: * with that name in no namespace.
0557: * </p>
0558: *
0559: * @param name the name of the attribute
0560: *
0561: * @return the attribute of this element with the specified name
0562: */
0563: public final Attribute getAttribute(String name) {
0564: return getAttribute(name, "");
0565: }
0566:
0567: /**
0568: * <p>
0569: * Returns the attribute with the specified name and namespace URI,
0570: * or null if this element does not have an attribute
0571: * with that name in that namespace.
0572: * </p>
0573: *
0574: * @param localName the local name of the attribute
0575: * @param namespaceURI the namespace of the attribute
0576: *
0577: * @return the attribute of this element
0578: * with the specified name and namespace
0579: */
0580: public final Attribute getAttribute(String localName,
0581: String namespaceURI) {
0582:
0583: if (attributes == null)
0584: return null;
0585: for (int i = 0; i < numAttributes; i++) {
0586: Attribute a = attributes[i];
0587: if (a.getLocalName().equals(localName)
0588: && a.getNamespaceURI().equals(namespaceURI)) {
0589: return a;
0590: }
0591: }
0592:
0593: return null;
0594:
0595: }
0596:
0597: /**
0598: * <p>
0599: * Returns the value of the attribute with the specified
0600: * name in no namespace,
0601: * or null if this element does not have an attribute
0602: * with that name.
0603: * </p>
0604: *
0605: * @param name the name of the attribute
0606: *
0607: * @return the value of the attribute of this element
0608: * with the specified name
0609: */
0610: public final String getAttributeValue(String name) {
0611: return getAttributeValue(name, "");
0612: }
0613:
0614: /**
0615: *
0616: * <p>
0617: * Returns the number of attributes of this <code>Element</code>,
0618: * not counting namespace declarations.
0619: * This is always a non-negative number.
0620: * </p>
0621: *
0622: * @return the number of attributes in the container
0623: */
0624: public final int getAttributeCount() {
0625: return numAttributes;
0626: }
0627:
0628: /**
0629: *
0630: * <p>
0631: * Selects an attribute by index.
0632: * The index is purely for convenience and has no particular
0633: * meaning. In particular, it is <em>not</em> necessarily the
0634: * position of this attribute in the original document from
0635: * which this <code>Element</code> object was read.
0636: * As with most lists in Java, attributes are numbered
0637: * from 0 to one less than the length of the list.
0638: * </p>
0639: *
0640: * <p>
0641: * In general, you should not add attributes to or remove
0642: * attributes from the list while iterating across it.
0643: * Doing so will change the indexes of the other attributes in
0644: * the list. it is, however, safe to remove an attribute from
0645: * either end of the list (0 or <code>getAttributeCount()-1</code>)
0646: * until there are no attributes left.
0647: * </p>
0648: *
0649: * @param index the attribute to return
0650: *
0651: * @return the index<sup>th</sup> attribute of this element
0652: *
0653: * @throws IndexOutofBoundsException if the index is negative
0654: * or greater than or equal to the number of attributes
0655: * of this element
0656: *
0657: */
0658: public final Attribute getAttribute(int index) {
0659:
0660: if (attributes == null) {
0661: throw new IndexOutOfBoundsException(
0662: "Element does not have any attributes");
0663: }
0664: return attributes[index];
0665:
0666: }
0667:
0668: /**
0669: * <p>
0670: * Returns the value of the attribute with the
0671: * specified name and namespace URI,
0672: * or null if this element does not have such an attribute.
0673: * </p>
0674: *
0675: * @param localName the name of the attribute
0676: * @param namespaceURI the namespace of the attribute
0677: *
0678: * @return the value of the attribute of this element
0679: * with the specified name and namespace
0680: */
0681: public final String getAttributeValue(String localName,
0682: String namespaceURI) {
0683:
0684: Attribute attribute = getAttribute(localName, namespaceURI);
0685: if (attribute == null)
0686: return null;
0687: else
0688: return attribute.getValue();
0689:
0690: }
0691:
0692: /**
0693: * <p>
0694: * Returns the local name of this element, not including the
0695: * namespace prefix or colon.
0696: * </p>
0697: *
0698: * @return the local name of this element
0699: */
0700: public final String getLocalName() {
0701: return localName;
0702: }
0703:
0704: /**
0705: * <p>
0706: * Returns the complete name of this element, including the
0707: * namespace prefix if this element has one.
0708: * </p>
0709: *
0710: * @return the qualified name of this element
0711: */
0712: public final String getQualifiedName() {
0713: if (prefix.length() == 0)
0714: return localName;
0715: else
0716: return prefix + ":" + localName;
0717: }
0718:
0719: /**
0720: * <p>
0721: * Returns the prefix of this element, or the empty string
0722: * if this element does not have a prefix.
0723: * </p>
0724: *
0725: * @return the prefix of this element
0726: */
0727: public final String getNamespacePrefix() {
0728: return prefix;
0729: }
0730:
0731: /**
0732: * <p>
0733: * Returns the namespace URI of this element,
0734: * or the empty string if this element is not
0735: * in a namespace.
0736: * </p>
0737: *
0738: * @return the namespace URI of this element
0739: */
0740: public final String getNamespaceURI() {
0741: return URI;
0742: }
0743:
0744: /**
0745: * <p>
0746: * Returns the namespace URI mapped to the specified
0747: * prefix within this element. Returns null if this prefix
0748: * is not associated with a URI.
0749: * </p>
0750: *
0751: * @param prefix the namespace prefix whose URI is desired
0752: *
0753: * @return the namespace URI mapped to <code>prefix</code>
0754: */
0755: public final String getNamespaceURI(String prefix) {
0756:
0757: Element current = this ;
0758: String result = getLocalNamespaceURI(prefix);
0759: while (result == null) {
0760: ParentNode parent = current.getParent();
0761: if (parent == null || parent.isDocument())
0762: break;
0763: current = (Element) parent;
0764: result = current.getLocalNamespaceURI(prefix);
0765: }
0766: if (result == null && "".equals(prefix))
0767: result = "";
0768: return result;
0769:
0770: }
0771:
0772: final String getLocalNamespaceURI(String prefix) {
0773:
0774: if (prefix.equals(this .prefix))
0775: return this .URI;
0776:
0777: if ("xml".equals(prefix)) {
0778: return "http://www.w3.org/XML/1998/namespace";
0779: }
0780: // This next line uses the original Namespaces 1.0
0781: // specification rules.
0782: // Namespaces 1.0 + errata is different
0783: if ("xmlns".equals(prefix))
0784: return "";
0785: // Look in the additional namespace declarations
0786: if (namespaces != null) {
0787: String result = namespaces.getURI(prefix);
0788: if (result != null)
0789: return result;
0790: }
0791: // Look in the attributes
0792: if (prefix.length() != 0 && attributes != null) {
0793: for (int i = 0; i < numAttributes; i++) {
0794: Attribute a = attributes[i];
0795: if (a.getNamespacePrefix().equals(prefix)) {
0796: return a.getNamespaceURI();
0797: }
0798: }
0799: }
0800:
0801: return null;
0802:
0803: }
0804:
0805: /**
0806: * <p>
0807: * Sets the local name of this element.
0808: * </p>
0809: *
0810: * @param localName the new local name
0811: *
0812: * @throws IllegalNameException if <code>localName</code> is not
0813: * a legal, non-colonized name
0814: */
0815: public void setLocalName(String localName) {
0816: _setLocalName(localName);
0817: }
0818:
0819: private void _setLocalName(String localName) {
0820: Verifier.checkNCName(localName);
0821: this .localName = localName;
0822: }
0823:
0824: /**
0825: * <p>
0826: * Sets the namespace URI of this element.
0827: * </p>
0828: *
0829: * @param uri the new namespace URI
0830: *
0831: * @throws MalformedURIException if <code>uri</code>
0832: * is not an absolute RFC 3986 URI reference
0833: * @throws NamespaceException if this element has a prefix
0834: * and <code>uri</code> is null or the empty string;
0835: * or if the element's prefix is shared by an attribute
0836: * or additional namespace
0837: */
0838: public void setNamespaceURI(String uri) {
0839: _setNamespaceURI(uri);
0840: }
0841:
0842: private void _setNamespaceURI(String uri) {
0843:
0844: if (uri == null)
0845: uri = "";
0846: // Next line is needed to avoid unintentional
0847: // exceptions below when checking for conflicts
0848: if (uri.equals(this .URI))
0849: return;
0850: if (uri.length() == 0) { // faster than "".equals(uri)
0851: if (prefix.length() != 0) {
0852: throw new NamespaceConflictException(
0853: "Prefixed elements must have namespace URIs.");
0854: }
0855: } else
0856: Verifier.checkAbsoluteURIReference(uri);
0857: // Make sure this doesn't conflict with any local
0858: // attribute prefixes or additional namespace declarations
0859: // Note that if the prefix equals the prefix, then the
0860: // URI must equal the old URI, so the URI can't easily be
0861: // changed. (you'd need to detach everything first;
0862: // change the URIs, then put it all back together
0863: if (namespaces != null) {
0864: String result = namespaces.getURI(prefix);
0865: if (result != null) {
0866: throw new NamespaceConflictException(
0867: "new URI conflicts with existing prefix");
0868: }
0869: }
0870: // Look in the attributes
0871: if (uri.length() > 0 && attributes != null) {
0872: for (int i = 0; i < numAttributes; i++) {
0873: Attribute a = attributes[i];
0874: String attPrefix = a.getNamespacePrefix();
0875: if (attPrefix.length() == 0)
0876: continue;
0877: if (a.getNamespacePrefix().equals(prefix)) {
0878: throw new NamespaceConflictException(
0879: "new element URI " + uri
0880: + " conflicts with attribute "
0881: + a.getQualifiedName());
0882: }
0883: }
0884: }
0885:
0886: if ("http://www.w3.org/XML/1998/namespace".equals(uri)
0887: && !"xml".equals(prefix)) {
0888: throw new NamespaceConflictException(
0889: "Wrong prefix "
0890: + prefix
0891: + " for the http://www.w3.org/XML/1998/namespace namespace URI");
0892: } else if ("xml".equals(prefix)
0893: && !"http://www.w3.org/XML/1998/namespace".equals(uri)) {
0894: throw new NamespaceConflictException("Wrong namespace URI "
0895: + uri + " for the xml prefix");
0896: }
0897:
0898: this .URI = uri;
0899:
0900: }
0901:
0902: /**
0903: * <p>
0904: * Sets the namespace prefix of this element.
0905: * You can pass null or the empty string to remove the prefix.
0906: * </p>
0907: *
0908: * @param prefix the new namespace prefix
0909: *
0910: * @throws IllegalNameException if <code>prefix</code> is
0911: * not a legal XML non-colonized name
0912: * @throws NamespaceConflictException if <code>prefix</code> is
0913: * already in use by an attribute or additional
0914: * namespace with a different URI than the element
0915: * itself
0916: */
0917: public void setNamespacePrefix(String prefix) {
0918: _setNamespacePrefix(prefix);
0919: }
0920:
0921: private void _setNamespacePrefix(String prefix) {
0922:
0923: if (prefix == null)
0924: prefix = "";
0925:
0926: if (prefix.length() != 0)
0927: Verifier.checkNCName(prefix);
0928:
0929: // Check how this affects or conflicts with
0930: // attribute namespaces and additional
0931: // namespace declarations.
0932: String uri = getLocalNamespaceURI(prefix);
0933: if (uri != null) {
0934: if (!uri.equals(this .URI) && !"xml".equals(prefix)) {
0935: throw new NamespaceConflictException(prefix
0936: + " conflicts with existing prefix");
0937: }
0938: } else if ("".equals(this .URI) && !"".equals(prefix)) {
0939: throw new NamespaceConflictException(
0940: "Cannot assign prefix to element in no namespace");
0941: }
0942:
0943: this .prefix = prefix;
0944:
0945: }
0946:
0947: /**
0948: * <p>
0949: * Inserts a child node at the specified position.
0950: * Inserting at position 0 makes the child the first child
0951: * of this node. Inserting at the position
0952: * <code>getChildCount()</code>
0953: * makes the child the last child of the node.
0954: * </p>
0955: *
0956: * <p>
0957: * All the other methods that add a node to the tree,
0958: * invoke this method ultimately.
0959: * </p>
0960: *
0961: * @param position where to insert the child
0962: * @param child the node to insert
0963: *
0964: * @throws IllegalAddException if <code>child</code>
0965: * is a <code>Document</code>
0966: * @throws MultipleParentException if <code>child</code>
0967: * already has a parent
0968: * @throws NullPointerException if <code>child</code> is null
0969: * @throws IndexOutOfBoundsException if the position is negative
0970: * or greater than the number of children of this element.
0971: */
0972: void insertionAllowed(Node child, int position) {
0973:
0974: if (child == null) {
0975: throw new NullPointerException(
0976: "Tried to insert a null child in the tree");
0977: } else if (child.getParent() != null) {
0978: throw new MultipleParentException(child.toString()
0979: + " child already has a parent.");
0980: } else if (child.isElement()) {
0981: checkCycle(child, this );
0982: return;
0983: } else if (child.isText() || child.isProcessingInstruction()
0984: || child.isComment()) {
0985: return;
0986: } else {
0987: throw new IllegalAddException("Cannot add a "
0988: + child.getClass().getName() + " to an Element.");
0989: }
0990:
0991: }
0992:
0993: private static void checkCycle(Node child, ParentNode parent) {
0994:
0995: if (child == parent) {
0996: throw new CycleException("Cannot add a node to itself");
0997: }
0998: if (child.getChildCount() == 0)
0999: return;
1000: while ((parent = parent.getParent()) != null) {
1001: if (parent == child) {
1002: throw new CycleException(
1003: "Cannot add an ancestor as a child");
1004: }
1005: }
1006:
1007: }
1008:
1009: /**
1010: * <p>
1011: * Converts a string to a text node and inserts that
1012: * node at the specified position.
1013: * </p>
1014: *
1015: * @param position where to insert the child
1016: * @param text the string to convert to a text node and insert
1017: *
1018: * @throws NullPointerException if text is null
1019: * @throws IndexOutOfBoundsException if the position is negative
1020: * or greater than the number of children of the node
1021: */
1022: public void insertChild(String text, int position) {
1023:
1024: if (text == null) {
1025: throw new NullPointerException("Inserted null string");
1026: }
1027: super .fastInsertChild(new Text(text), position);
1028:
1029: }
1030:
1031: /**
1032: * <p>
1033: * Converts a string to a text node
1034: * and appends that node to the children of this node.
1035: * </p>
1036: *
1037: * @param text String to add to this node
1038: *
1039: * @throws IllegalAddException if this node cannot
1040: * have children of this type
1041: * @throws NullPointerException if <code>text</code> is null
1042: */
1043: public void appendChild(String text) {
1044: insertChild(new Text(text), getChildCount());
1045: }
1046:
1047: /**
1048: * <p>
1049: * Detaches all children from this node.
1050: * </p>
1051: *
1052: * <p>
1053: * Subclassers should note that the default implementation of this
1054: * method does <strong>not</strong> call <code>removeChild</code>
1055: * or <code>detach</code>. If you override
1056: * <code>removeChild</code>, you'll probably need to override this
1057: * method as well.
1058: * </p>
1059: *
1060: * @return a list of all the children removed in the order they
1061: * appeared in the element
1062: */
1063: public Nodes removeChildren() {
1064:
1065: int length = this .getChildCount();
1066: Nodes result = new Nodes();
1067: for (int i = 0; i < length; i++) {
1068: Node child = getChild(i);
1069: if (child.isElement())
1070: fillInBaseURI((Element) child);
1071: child.setParent(null);
1072: result.append(child);
1073: }
1074: this .children = null;
1075: this .childCount = 0;
1076:
1077: return result;
1078:
1079: }
1080:
1081: /**
1082: * <p>
1083: * Declares a namespace prefix. This is only necessary when
1084: * prefixes are used in element content and attribute values,
1085: * as in XSLT and the W3C XML Schema Language. Do not use
1086: * this method to declare prefixes for element and attribute
1087: * names.
1088: * </p>
1089: *
1090: * <p>
1091: * If you do redeclare a prefix that is already used
1092: * by an element or attribute name, the additional
1093: * namespace is added if and only if the URI is the same.
1094: * Conflicting namespace declarations will throw an exception.
1095: * </p>
1096: *
1097: * @param prefix the prefix to declare
1098: * @param uri the absolute URI reference to map the prefix to
1099: *
1100: * @throws MalformedURIException if <code>URI</code>
1101: * is not an RFC 3986 URI reference
1102: * @throws IllegalNameException if <code>prefix</code> is not
1103: * a legal XML non-colonized name
1104: * @throws NamespaceConflictException if the mapping conflicts
1105: * with an existing element, attribute,
1106: * or additional namespace declaration
1107: */
1108: public void addNamespaceDeclaration(String prefix, String uri) {
1109:
1110: if (prefix == null)
1111: prefix = "";
1112: if (uri == null)
1113: uri = "";
1114:
1115: // check to see if this is the xmlns or xml prefix
1116: if (prefix.equals("xmlns")) {
1117: if (uri.equals("")) {
1118: // This is done automatically
1119: return;
1120: }
1121: throw new NamespaceConflictException(
1122: "The xmlns prefix cannot bound to any URI");
1123: } else if (prefix.equals("xml")) {
1124: if (uri.equals("http://www.w3.org/XML/1998/namespace")) {
1125: // This is done automatically
1126: return;
1127: }
1128: throw new NamespaceConflictException(
1129: "Wrong namespace URI for xml prefix: " + uri);
1130: } else if (uri.equals("http://www.w3.org/XML/1998/namespace")) {
1131: throw new NamespaceConflictException(
1132: "Wrong prefix for http://www.w3.org/XML/1998/namespace namespace: "
1133: + prefix);
1134: }
1135:
1136: if (prefix.length() != 0) {
1137: Verifier.checkNCName(prefix);
1138: Verifier.checkAbsoluteURIReference(uri);
1139: } else if (uri.length() != 0) {
1140: // Make sure we're not trying to undeclare
1141: // the default namespace; this is legal.
1142: Verifier.checkAbsoluteURIReference(uri);
1143: }
1144:
1145: String currentBinding = getLocalNamespaceURI(prefix);
1146: if (currentBinding != null && !currentBinding.equals(uri)) {
1147:
1148: String message;
1149: if (prefix.equals("")) {
1150: message = "Additional namespace " + uri
1151: + " conflicts with existing default namespace "
1152: + currentBinding;
1153: } else {
1154: message = "Additional namespace " + uri
1155: + " for the prefix " + prefix
1156: + " conflicts with existing namespace binding "
1157: + currentBinding;
1158: }
1159: throw new NamespaceConflictException(message);
1160: }
1161:
1162: if (namespaces == null)
1163: namespaces = new Namespaces();
1164: namespaces.put(prefix, uri);
1165:
1166: }
1167:
1168: /**
1169: * <p>
1170: * Removes the mapping of the specified prefix. This method only
1171: * removes additional namespaces added with
1172: * <code>addNamespaceDeclaration</code>.
1173: * It has no effect on namespaces of elements and attributes.
1174: * If the prefix is not used on this element, this method
1175: * does nothing.
1176: * </p>
1177: *
1178: * @param prefix the prefix whose declaration should be removed
1179: */
1180: public void removeNamespaceDeclaration(String prefix) {
1181:
1182: if (namespaces != null) {
1183: namespaces.remove(prefix);
1184: }
1185:
1186: }
1187:
1188: /**
1189: * <p>
1190: * Returns the number of namespace declarations on this
1191: * element. This counts the namespace of the element
1192: * itself (which may be the empty string), the namespace
1193: * of each attribute, and each namespace added
1194: * by <code>addNamespaceDeclaration</code>.
1195: * However, prefixes used multiple times are only counted
1196: * once; and the <code>xml</code> prefix used for
1197: * <code>xml:base</code>, <code>xml:lang</code>, and
1198: * <code>xml:space</code> is not counted even if one of these
1199: * attributes is present on the element.
1200: * </p>
1201: *
1202: * <p>
1203: * The return value is almost always positive. It can be zero
1204: * if and only if the element itself has the prefix
1205: * <code>xml</code>; e.g. <code><xml:space /></code>.
1206: * This is not endorsed by the XML specification. The prefix
1207: * <code>xml</code> is reserved for use by the W3C, which has only
1208: * used it for attributes to date. You really shouldn't do this.
1209: * Nonetheless, this is not malformed so XOM allows it.
1210: * </p>
1211: *
1212: * @return the number of namespaces declared by this element
1213: */
1214: public final int getNamespaceDeclarationCount() {
1215:
1216: // This seems to be a hot spot for DOM conversion.
1217: // I'm trying to avoid the overhead of creating and adding
1218: // to a HashSet for the simplest case of an element, none
1219: // of whose attributes are in namespaces, and which has no
1220: // additional namespace declarations. In this case, the
1221: // namespace count is exactly one, which is here indicated
1222: // by a null prefix set.
1223: Set allPrefixes = null;
1224: if (namespaces != null) {
1225: allPrefixes = new HashSet(namespaces.getPrefixes());
1226: allPrefixes.add(prefix);
1227: }
1228: if ("xml".equals(prefix))
1229: allPrefixes = new HashSet();
1230: // add attribute prefixes
1231: int count = getAttributeCount();
1232: for (int i = 0; i < count; i++) {
1233: Attribute att = getAttribute(i);
1234: String attPrefix = att.getNamespacePrefix();
1235: if (attPrefix.length() != 0 && !"xml".equals(attPrefix)) {
1236: if (allPrefixes == null) {
1237: allPrefixes = new HashSet();
1238: allPrefixes.add(prefix);
1239: }
1240: allPrefixes.add(attPrefix);
1241: }
1242: }
1243:
1244: if (allPrefixes == null)
1245: return 1;
1246: return allPrefixes.size();
1247:
1248: }
1249:
1250: // Used for XPath and serialization
1251: Map getNamespacePrefixesInScope() {
1252:
1253: HashMap namespaces = new HashMap();
1254:
1255: Element current = this ;
1256: while (current != null) {
1257:
1258: if (!("xml".equals(current.prefix))) {
1259: addPrefixIfNotAlreadyPresent(namespaces, current,
1260: current.prefix);
1261: }
1262:
1263: // add attribute prefixes
1264: int count = current.getAttributeCount();
1265: for (int i = 0; i < count; i++) {
1266: Attribute att = current.getAttribute(i);
1267: String attPrefix = att.getNamespacePrefix();
1268: if (attPrefix.length() != 0
1269: && !("xml".equals(attPrefix))) {
1270: addPrefixIfNotAlreadyPresent(namespaces, current,
1271: attPrefix);
1272: }
1273: }
1274:
1275: // add additional namespace prefixes
1276: // ???? should add more methods to Namespaces to avoid access to private data
1277: if (current.namespaces != null) {
1278: int namespaceCount = current.namespaces.size();
1279: for (int i = 0; i < namespaceCount; i++) {
1280: String nsPrefix = current.namespaces.getPrefix(i);
1281: addPrefixIfNotAlreadyPresent(namespaces, current,
1282: nsPrefix);
1283: }
1284: }
1285:
1286: ParentNode parent = current.getParent();
1287: if (parent == null || parent.isDocument()
1288: || parent.isDocumentFragment()) {
1289: break;
1290: }
1291: current = (Element) parent;
1292: }
1293:
1294: return namespaces;
1295:
1296: }
1297:
1298: private void addPrefixIfNotAlreadyPresent(HashMap namespaces,
1299: Element current, String prefix) {
1300: if (!namespaces.containsKey(prefix)) {
1301: namespaces
1302: .put(prefix, current.getLocalNamespaceURI(prefix));
1303: }
1304: }
1305:
1306: /**
1307: * <p>
1308: * Returns the index<sup>th</sup> namespace prefix <em>declared</em> on
1309: * this element. Namespaces inherited from ancestors are not included.
1310: * The index is purely for convenience, and has no
1311: * meaning in itself. This includes the namespaces of the element
1312: * name and of all attributes' names (except for those with the
1313: * prefix <code>xml</code> such as <code>xml:space</code>) as well
1314: * as additional declarations made for attribute values and element
1315: * content. However, prefixes used multiple times (e.g. on several
1316: * attribute values) are only reported once. The default
1317: * namespace is reported with an empty string prefix if present.
1318: * Like most lists in Java, the first prefix is at index 0.
1319: * </p>
1320: *
1321: * <p>
1322: * If the namespaces on the element change for any reason
1323: * (adding or removing an attribute in a namespace, adding
1324: * or removing a namespace declaration, changing the prefix
1325: * of an element, etc.) then then this method may skip or
1326: * repeat prefixes. Don't change the prefixes of an element
1327: * while iterating across them.
1328: * </p>
1329: *
1330: * @param index the prefix to return
1331: *
1332: * @return the prefix
1333: *
1334: * @throws IndexOutOfBoundsException if <code>index</code> is
1335: * negative or greater than or equal to the number of
1336: * namespaces declared by this element.
1337: *
1338: */
1339: public final String getNamespacePrefix(int index) {
1340:
1341: SortedSet allPrefixes;
1342: if (namespaces != null) {
1343: allPrefixes = new TreeSet(namespaces.getPrefixes());
1344: } else
1345: allPrefixes = new TreeSet();
1346:
1347: if (!("xml".equals(prefix)))
1348: allPrefixes.add(prefix);
1349:
1350: // add attribute prefixes
1351: for (int i = 0; i < getAttributeCount(); i++) {
1352: Attribute att = getAttribute(i);
1353: String attPrefix = att.getNamespacePrefix();
1354: if (attPrefix.length() != 0 && !("xml".equals(attPrefix))) {
1355: allPrefixes.add(attPrefix);
1356: }
1357: }
1358:
1359: Iterator iterator = allPrefixes.iterator();
1360: try {
1361: for (int i = 0; i < index; i++) {
1362: iterator.next();
1363: }
1364: return (String) iterator.next();
1365: } catch (NoSuchElementException ex) {
1366: throw new IndexOutOfBoundsException("No " + index
1367: + "th namespace");
1368: }
1369:
1370: }
1371:
1372: /**
1373: *
1374: * <p>
1375: * Sets the URI from which this element was loaded,
1376: * and against which relative URLs in this element will be
1377: * resolved, unless an <code>xml:base</code> attribute overrides
1378: * this. Setting the base URI to null or the empty string removes
1379: * any existing base URI.
1380: * </p>
1381: *
1382: * @param URI the new base URI for this element
1383: *
1384: * @throws MalformedURIException if <code>URI</code> is
1385: * not a legal RFC 3986 absolute URI
1386: */
1387: public void setBaseURI(String URI) {
1388: setActualBaseURI(URI);
1389: }
1390:
1391: /**
1392: *
1393: * <p>
1394: * Returns the absolute base URI against which relative URIs in
1395: * this element should be resolved. <code>xml:base</code>
1396: * attributes <em>in the same entity</em> take precedence over the
1397: * actual base URI of the document where the element was found
1398: * or which was set by <code>setBaseURI</code>.
1399: * </p>
1400: *
1401: * <p>
1402: * This URI is made absolute before it is returned
1403: * by resolving the information in this element against the
1404: * information in its parent element and document entity.
1405: * However, it is not always possible to fully absolutize the
1406: * URI in all circumstances. In this case, this method returns the
1407: * empty string to indicate the base URI of the current entity.
1408: * </p>
1409: *
1410: * <p>
1411: * If the element's <code>xml:base</code> attribute contains a
1412: * value that is a syntactically illegal URI (e.g. %GF.html"),
1413: * then the base URI is application dependent. XOM's choice is
1414: * to behave as if the element did not have an <code>xml:base</code>
1415: * attribute.
1416: * </p>
1417: *
1418: * @return the base URI of this element
1419: */
1420: public String getBaseURI() {
1421:
1422: String baseURI = "";
1423: String sourceEntity = this .getActualBaseURI();
1424:
1425: ParentNode current = this ;
1426:
1427: while (true) {
1428: String currentEntity = current.getActualBaseURI();
1429: if (sourceEntity.length() != 0
1430: && !sourceEntity.equals(currentEntity)) {
1431: baseURI = URIUtil.absolutize(sourceEntity, baseURI);
1432: break;
1433: }
1434:
1435: if (current.isDocument()) {
1436: baseURI = URIUtil.absolutize(currentEntity, baseURI);
1437: break;
1438: }
1439: Attribute baseAttribute = ((Element) current).getAttribute(
1440: "base", "http://www.w3.org/XML/1998/namespace");
1441: if (baseAttribute != null) {
1442: String baseIRI = baseAttribute.getValue();
1443: // The base attribute contains an IRI, not a URI.
1444: // Thus the first thing we have to do is escape it
1445: // to convert illegal characters to hexadecimal escapes.
1446: String base = URIUtil.toURI(baseIRI);
1447: if ("".equals(base)) {
1448: baseURI = getEntityURI();
1449: break;
1450: } else if (legalURI(base)) {
1451: if ("".equals(baseURI))
1452: baseURI = base;
1453: else if (URIUtil.isOpaque(base))
1454: break;
1455: else
1456: baseURI = URIUtil.absolutize(base, baseURI);
1457: if (URIUtil.isAbsolute(base))
1458: break; // ???? base or baseURI
1459: }
1460: }
1461: current = current.getParent();
1462: if (current == null) {
1463: baseURI = URIUtil.absolutize(currentEntity, baseURI);
1464: break;
1465: }
1466: }
1467:
1468: if (URIUtil.isAbsolute(baseURI))
1469: return baseURI;
1470: return "";
1471:
1472: }
1473:
1474: private String getEntityURI() {
1475:
1476: ParentNode current = this ;
1477: while (current != null) {
1478: if (current.actualBaseURI != null
1479: && current.actualBaseURI.length() != 0) {
1480: return current.actualBaseURI;
1481: }
1482: current = current.getParent();
1483: }
1484: return "";
1485:
1486: }
1487:
1488: private boolean legalURI(String base) {
1489:
1490: try {
1491: Verifier.checkURIReference(base);
1492: return true;
1493: } catch (MalformedURIException ex) {
1494: return false;
1495: }
1496:
1497: }
1498:
1499: /**
1500: * <p>
1501: * Returns a string containing the XML serialization of this
1502: * element. This includes the element and all its attributes
1503: * and descendants. However, it does not contain namespace
1504: * declarations for namespaces inherited from ancestor elements.
1505: * </p>
1506: *
1507: * @return the XML representation of this element
1508: *
1509: */
1510: public final String toXML() {
1511:
1512: StringBuffer result = new StringBuffer(1024);
1513: Node current = this ;
1514: boolean endTag = false;
1515: int index = -1;
1516: int[] indexes = new int[10];
1517: int top = 0;
1518: indexes[0] = -1;
1519:
1520: while (true) {
1521:
1522: if (!endTag && current.getChildCount() > 0) {
1523: writeStartTag((Element) current, result);
1524: current = current.getChild(0);
1525: index = 0;
1526: top++;
1527: indexes = grow(indexes, top);
1528: indexes[top] = 0;
1529: } else {
1530: if (endTag) {
1531: writeEndTag((Element) current, result);
1532: if (current == this )
1533: break;
1534: } else if (current.isElement()) {
1535: writeStartTag((Element) current, result);
1536: if (current == this )
1537: break;
1538: } else {
1539: result.append(current.toXML());
1540: }
1541: endTag = false;
1542: ParentNode parent = current.getParent();
1543: if (parent.getChildCount() - 1 == index) {
1544: current = parent;
1545: top--;
1546: if (current != this ) {
1547: index = indexes[top];
1548: }
1549: endTag = true;
1550: } else {
1551: index++;
1552: indexes[top] = index;
1553: current = parent.getChild(index);
1554: }
1555:
1556: }
1557:
1558: }
1559:
1560: return result.toString();
1561:
1562: }
1563:
1564: private static void writeStartTag(Element element,
1565: StringBuffer result) {
1566:
1567: result.append("<");
1568: result.append(element.getQualifiedName());
1569:
1570: ParentNode parentNode = element.getParent();
1571: for (int i = 0; i < element.getNamespaceDeclarationCount(); i++) {
1572: String additionalPrefix = element.getNamespacePrefix(i);
1573: String uri = element.getNamespaceURI(additionalPrefix);
1574: if (parentNode != null && parentNode.isElement()) {
1575: Element parentElement = (Element) parentNode;
1576: if (uri.equals(parentElement
1577: .getNamespaceURI(additionalPrefix))) {
1578: continue;
1579: }
1580: } else if (uri.length() == 0) {
1581: continue; // no need to say xmlns=""
1582: }
1583:
1584: result.append(" xmlns");
1585: if (additionalPrefix.length() > 0) {
1586: result.append(':');
1587: result.append(additionalPrefix);
1588: }
1589: result.append("=\"");
1590: result.append(uri);
1591: result.append('"');
1592: }
1593:
1594: // attributes
1595: if (element.attributes != null) {
1596: for (int i = 0; i < element.numAttributes; i++) {
1597: Attribute attribute = element.attributes[i];
1598: result.append(' ');
1599: result.append(attribute.toXML());
1600: }
1601: }
1602:
1603: if (element.getChildCount() > 0) {
1604: result.append('>');
1605: } else {
1606: result.append(" />");
1607: }
1608:
1609: }
1610:
1611: private static void writeEndTag(Element element, StringBuffer result) {
1612:
1613: result.append("</");
1614: result.append(element.getQualifiedName());
1615: result.append(">");
1616:
1617: }
1618:
1619: /**
1620: * <p>
1621: * Returns the value of the element as defined by XPath 1.0.
1622: * This is the complete PCDATA content of the element, without
1623: * any tags, comments, or processing instructions after all
1624: * entity and character references have been resolved.
1625: * </p>
1626: *
1627: * @return XPath string value of this element
1628: *
1629: */
1630: public final String getValue() {
1631:
1632: // non-recursive algorithm avoids stack size limitations
1633: int childCount = this .getChildCount();
1634: if (childCount == 0)
1635: return "";
1636:
1637: Node current = this .getChild(0);
1638: // optimization for common case where element
1639: // has a single text node child
1640: if (childCount == 1 && current.isText()) {
1641: return current.getValue();
1642: }
1643:
1644: StringBuffer result = new StringBuffer();
1645: int index = 0;
1646: int[] indexes = new int[10];
1647: int top = 0;
1648: indexes[0] = 0;
1649:
1650: boolean endTag = false;
1651: while (true) {
1652: if (!endTag && current.getChildCount() > 0) {
1653: current = current.getChild(0);
1654: index = 0;
1655: top++;
1656: indexes = grow(indexes, top);
1657: indexes[top] = 0;
1658: } else {
1659: endTag = false;
1660: if (current.isText())
1661: result.append(current.getValue());
1662: ParentNode parent = current.getParent();
1663: if (parent.getChildCount() - 1 == index) {
1664: current = parent;
1665: top--;
1666: if (current == this )
1667: break;
1668: index = indexes[top];
1669: endTag = true;
1670: } else {
1671: index++;
1672: indexes[top] = index;
1673: current = parent.getChild(index);
1674: }
1675: }
1676: }
1677:
1678: return result.toString();
1679:
1680: }
1681:
1682: /**
1683: * <p>
1684: * Creates a deep copy of this element with no parent,
1685: * that can be added to this document or a different one.
1686: * </p>
1687: *
1688: * <p>
1689: * Subclassers should be wary. Implementing this method is trickier
1690: * than it might seem, especially if you wish to avoid potential
1691: * stack overflows in deep documents. In particular, you should not
1692: * rely on the obvious recursive algorithm. Most subclasses should
1693: * override the {@link nu.xom.Element#shallowCopy() shallowCopy}
1694: * method instead.
1695: * </p>
1696: *
1697: * @return a deep copy of this element with no parent
1698: */
1699: public Node copy() {
1700: Element result = copyTag(this );
1701: copyChildren(this , result);
1702: return result;
1703: }
1704:
1705: /**
1706: * <p>
1707: * Creates a very shallow copy of the element with the same name
1708: * and namespace URI, but no children, attributes, base URI, or
1709: * namespace declaration. This method is invoked as necessary
1710: * by the {@link nu.xom.Element#copy() copy} method
1711: * and the {@link nu.xom.Element#Element(nu.xom.Element)
1712: * copy constructor}.
1713: * </p>
1714: *
1715: * <p>
1716: * Subclasses should override this method so that it
1717: * returns an instance of the subclass so that types
1718: * are preserved when copying. This method should not add any
1719: * attributes, namespace declarations, or children to the
1720: * shallow copy. Any such items will be overwritten.
1721: * </p>
1722: *
1723: * @return an empty element with the same name and
1724: * namespace as this element
1725: */
1726: protected Element shallowCopy() {
1727: return new Element(getQualifiedName(), getNamespaceURI());
1728: }
1729:
1730: /**
1731: * <p>
1732: * Returns a string representation of this element suitable
1733: * for debugging and diagnosis. This is <em>not</em>
1734: * the XML representation of the element.
1735: * </p>
1736: *
1737: * @return a non-XML string representation of this element
1738: */
1739: public final String toString() {
1740: return "[" + getClass().getName() + ": " + getQualifiedName()
1741: + "]";
1742: }
1743:
1744: boolean isElement() {
1745: return true;
1746: }
1747:
1748: private void checkPrefixConflict(Attribute attribute) {
1749:
1750: String prefix = attribute.getNamespacePrefix();
1751: String namespaceURI = attribute.getNamespaceURI();
1752:
1753: // Look for conflicts
1754: for (int i = 0; i < numAttributes; i++) {
1755: Attribute a = attributes[i];
1756: if (a.getNamespacePrefix().equals(prefix)) {
1757: if (a.getNamespaceURI().equals(namespaceURI))
1758: return;
1759: throw new NamespaceConflictException("Prefix of "
1760: + attribute.getQualifiedName()
1761: + " conflicts with " + a.getQualifiedName());
1762: }
1763: }
1764:
1765: }
1766:
1767: Iterator attributeIterator() {
1768:
1769: return new Iterator() {
1770:
1771: private int next = 0;
1772:
1773: public boolean hasNext() {
1774: return next < numAttributes;
1775: }
1776:
1777: public Object next() throws NoSuchElementException {
1778:
1779: if (hasNext()) {
1780: Attribute a = attributes[next];
1781: next++;
1782: return a;
1783: }
1784: throw new NoSuchElementException("No such attribute");
1785:
1786: }
1787:
1788: public void remove() {
1789: throw new UnsupportedOperationException();
1790: }
1791:
1792: };
1793: }
1794:
1795: }
|