1
/* Copyright 2002-2005 Elliotte Rusty Harold
3
This library is free software; you can redistribute it and/or modify
4
it under the terms of version 2.1 of the GNU Lesser General Public
5
License as published by the Free Software Foundation.
7
This library is distributed in the hope that it will be useful,
8
but WITHOUT ANY WARRANTY; without even the implied warranty of
9
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
GNU Lesser General Public License for more details.
12
You should have received a copy of the GNU Lesser General Public
13
License along with this library; if not, write to the
14
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
15
Boston, MA 02111-1307 USA
17
You can contact Elliotte Rusty Harold by sending e-mail to
18
elharo@metalab.unc.edu. Please include the word "XOM" in the
19
subject line. The XOM home page is located at http://www.xom.nu/
24
import java.util.HashMap;
25
import java.util.HashSet;
26
import java.util.Iterator;
28
import java.util.NoSuchElementException;
29
// ???? Why do I still need these imports? Could
30
// I get rid of them? In particular could I get rid of sorting requirement on sets?
32
import java.util.SortedSet;
33
import java.util.TreeSet;
37
* This class represents an XML element. Each
38
* element has the following properties:
43
* <li>Prefix (which may be null or the empty string) </li>
44
* <li>Namespace URI (which may be null or the empty string) </li>
45
* <li>A list of attributes</li>
46
* <li>A list of namespace declarations for this element
47
* (not including those inherited from its parent)</li>
48
* <li>A list of child nodes</li>
51
* @author Elliotte Rusty Harold
55
public class Element extends ParentNode {
57
private String localName;
58
private String prefix;
61
private Attribute[] attributes = null;
62
private int numAttributes = 0;
63
private Namespaces namespaces = null;
67
* Creates a new element in no namespace.
70
* @param name the name of the element
72
* @throws IllegalNameException if <code>name</code>
73
* is not a legal XML 1.0 non-colonized name
75
public Element(String name) {
82
* Creates a new element in a namespace.
85
* @param name the qualified name of the element
86
* @param uri the namespace URI of the element
88
* @throws IllegalNameException if <code>name</code>
89
* is not a legal XML 1.0 name
90
* @throws NamespaceConflictException if <code>name</code>'s prefix
91
* cannot be used with <code>uri</code>
92
* @throws MalformedURIException if <code>uri</code>
93
* is not an RFC 3986 absolute URI reference
95
public Element(String name, String uri) {
97
// The shadowing is important here.
98
// I don't want to set the prefix field just yet.
100
String localName = name;
101
int colon = name.indexOf(':');
103
prefix = name.substring(0, colon);
104
localName = name.substring(colon + 1);
107
// The order of these next two calls
108
// matters a great deal.
109
_setNamespacePrefix(prefix);
110
_setNamespaceURI(uri);
112
_setLocalName(localName);
114
catch (IllegalNameException ex) {
125
static Element build(String name, String uri, String localName) {
127
Element result = new Element();
129
int colon = name.indexOf(':');
131
prefix = name.substring(0, colon);
133
result.prefix = prefix;
134
result.localName = localName;
135
// We do need to verify the URI here because parsers are
136
// allowing relative URIs which XOM forbids, for reasons
137
// of canonical XML if nothing else. But we only have to verify
138
// that it's an absolute base URI. I don't have to verify
140
if (! "".equals(uri)) Verifier.checkAbsoluteURIReference(uri);
149
* Creates a deep copy of an element.
150
* The copy is disconnected from the tree, and does not
154
* @param element the element to copy
157
public Element(Element element) {
159
this.prefix = element.prefix;
160
this.localName = element.localName;
161
this.URI = element.URI;
163
// Attach additional namespaces
164
if (element.namespaces != null) {
165
this.namespaces = element.namespaces.copy();
168
// Attach clones of attributes
169
if (element.attributes != null) {
170
this.attributes = element.copyAttributes(this);
171
this.numAttributes = element.numAttributes;
174
this.actualBaseURI = element.findActualBaseURI();
176
copyChildren(element, this);
181
private Attribute[] copyAttributes(Element newParent) {
183
Attribute[] copy = new Attribute[numAttributes];
184
for (int i = 0; i < numAttributes; i++) {
185
copy[i] = (Attribute) attributes[i].copy();
186
copy[i].setParent(newParent);
193
private static Element copyTag(final Element source) {
195
Element result = source.shallowCopy();
197
// Attach additional namespaces
198
if (source.namespaces != null) {
199
result.namespaces = source.namespaces.copy();
202
// Attach clones of attributes
203
if (source.attributes != null) {
204
result.attributes = source.copyAttributes(result);
205
result.numAttributes = source.numAttributes;
208
result.actualBaseURI = source.findActualBaseURI();
215
private static void copyChildren(final Element sourceElement,
216
Element resultElement) {
218
if (sourceElement.getChildCount() == 0) return;
219
ParentNode resultParent = resultElement;
220
Node sourceCurrent = sourceElement;
222
int[] indexes = new int[10];
226
// true if processing the element for the 2nd time;
227
// i.e. the element's end-tag
228
boolean endTag = false;
231
if (!endTag && sourceCurrent.getChildCount() > 0) {
232
sourceCurrent = sourceCurrent.getChild(0);
235
indexes = grow(indexes, top);
240
ParentNode sourceParent = sourceCurrent.getParent();
241
if (sourceParent.getChildCount() - 1 == index) {
242
sourceCurrent = sourceParent;
244
if (sourceCurrent == sourceElement) break;
246
resultParent = (Element) resultParent.getParent();
247
index = indexes[top];
253
indexes[top] = index;
254
sourceCurrent = sourceParent.getChild(index);
258
if (sourceCurrent.isElement()) {
259
Element child = copyTag((Element) sourceCurrent);
260
resultParent.appendChild(child);
261
if (sourceCurrent.getChildCount() > 0) {
262
resultParent = child;
266
Node child = sourceCurrent.copy();
267
resultParent.appendChild(child);
275
private static int[] grow(int[] indexes, int top) {
277
if (top < indexes.length) return indexes;
278
int[] result = new int[indexes.length*2];
279
System.arraycopy(indexes, 0, result, 0, indexes.length);
287
* Returns a list of the child elements of
288
* this element with the specified name in no namespace.
289
* The elements returned are in document order.
292
* @param name the name of the elements included in the list
294
* @return a comatose list containing the child elements of this
295
* element with the specified name
297
public final Elements getChildElements(String name) {
298
return getChildElements(name, "");
304
* Returns a list of the immediate child elements of this
305
* element with the specified local name and namespace URI.
306
* Passing the empty string or null as the local name
307
* returns all elements in the specified namespace.
308
* Passing null or the empty string as the namespace URI
309
* returns elements with the specified name in no namespace.
310
* The elements returned are in document order.
313
* @param localName the name of the elements included in the list
314
* @param namespaceURI the namespace URI of the elements included
317
* @return a comatose list containing the child
318
* elements of this element with the specified
319
* name in the specified namespace
321
public final Elements getChildElements(String localName,
322
String namespaceURI) {
324
if (namespaceURI == null) namespaceURI = "";
325
if (localName == null) localName = "";
327
Elements elements = new Elements();
328
for (int i = 0; i < getChildCount(); i++) {
329
Node child = getChild(i);
330
if (child.isElement()) {
331
Element element = (Element) child;
332
if ((localName.equals(element.getLocalName())
333
|| localName.length() == 0)
334
&& namespaceURI.equals(element.getNamespaceURI())) {
335
elements.add(element);
346
* Returns a list of all the child elements
347
* of this element in document order.
350
* @return a comatose list containing all
351
* child elements of this element
353
public final Elements getChildElements() {
355
Elements elements = new Elements();
356
for (int i = 0; i < getChildCount(); i++) {
357
Node child = getChild(i);
358
if (child.isElement()) {
359
Element element = (Element) child;
360
elements.add(element);
370
* Returns the first child
371
* element with the specified name in no namespace.
372
* If there is no such element, it returns null.
375
* @param name the name of the element to return
377
* @return the first child element with the specified local name
378
* in no namespace or null if there is no such element
380
public final Element getFirstChildElement(String name) {
381
return getFirstChildElement(name, "");
387
* Returns the first child
388
* element with the specified local name and namespace URI.
389
* If there is no such element, it returns null.
392
* @param localName the local name of the element to return
393
* @param namespaceURI the namespace URI of the element to return
395
* @return the first child with the specified local name in the
396
* specified namespace, or null if there is no such element
398
public final Element getFirstChildElement(String localName,
399
String namespaceURI) {
401
for (int i = 0; i < getChildCount(); i++) {
402
Node child = getChild(i);
403
if (child.isElement()) {
404
Element element = (Element) child;
405
if (localName.equals(element.getLocalName())
406
&& namespaceURI.equals(element.getNamespaceURI())) {
418
* Adds an attribute to this element, replacing any existing
419
* attribute with the same local name and namespace URI.
422
* @param attribute the attribute to add
424
* @throws MultipleParentException if the attribute is already
425
* attached to an element
426
* @throws NamespaceConflictException if the attribute's prefix
427
* is mapped to a different namespace URI than the same prefix
428
* is mapped to by this element, another attribute of
429
* this element, or an additional namespace declaration
432
public void addAttribute(Attribute attribute) {
434
if (attribute.getParent() != null) {
435
throw new MultipleParentException(
436
"Attribute already has a parent");
439
// check for namespace conflicts
440
String attPrefix = attribute.getNamespacePrefix();
441
if (attPrefix.length() != 0 && !"xml".equals(attPrefix)) {
442
if (prefix.equals(attribute.getNamespacePrefix())
443
&& !(getNamespaceURI()
444
.equals(attribute.getNamespaceURI()))) {
445
throw new NamespaceConflictException("Prefix of "
446
+ attribute.getQualifiedName()
447
+ " conflicts with element prefix " + prefix);
449
// check for conflicts with additional namespaces
450
if (namespaces != null) {
452
= namespaces.getURI(attribute.getNamespacePrefix());
454
&& !existing.equals(attribute.getNamespaceURI())) {
455
throw new NamespaceConflictException("Attribute prefix "
457
+ " conflicts with namespace declaration.");
463
if (attributes == null) attributes = new Attribute[1];
464
checkPrefixConflict(attribute);
466
// Is there already an attribute with this local name
467
// and namespace? If so, remove it.
468
Attribute oldAttribute = getAttribute(attribute.getLocalName(),
469
attribute.getNamespaceURI());
470
if (oldAttribute != null) remove(oldAttribute);
473
attribute.setParent(this);
478
private void add(Attribute attribute) {
480
if (numAttributes == attributes.length) {
481
Attribute[] newAttributes = new Attribute[attributes.length * 2];
482
System.arraycopy(attributes, 0, newAttributes, 0, numAttributes);
483
this.attributes = newAttributes;
485
attributes[numAttributes] = attribute;
491
private boolean remove(Attribute attribute) {
494
for (int i = 0; i < attributes.length; i++) {
495
if (attributes[i] == attribute) {
501
if (index == -1) return false;
503
int toCopy = numAttributes-index-1;
505
System.arraycopy(attributes, index+1, attributes, index, toCopy);
508
attributes[numAttributes] = null;
514
void fastAddAttribute(Attribute attribute) {
515
if (attributes == null) attributes = new Attribute[1];
517
attribute.setParent(this);
523
* Removes an attribute from this element.
526
* @param attribute the attribute to remove
528
* @return the attribute that was removed
530
* @throws NoSuchAttributeException if this element is not the
531
* parent of attribute
534
public Attribute removeAttribute(Attribute attribute) {
536
if (attributes == null) {
537
throw new NoSuchAttributeException(
538
"Tried to remove attribute "
539
+ attribute.getQualifiedName()
540
+ " from non-parent element");
542
if (attribute == null) {
543
throw new NullPointerException(
544
"Tried to remove null attribute");
546
if (remove(attribute)) {
547
attribute.setParent(null);
551
throw new NoSuchAttributeException(
552
"Tried to remove attribute "
553
+ attribute.getQualifiedName()
554
+ " from non-parent element");
562
* Returns the attribute with the specified name in no namespace,
563
* or null if this element does not have an attribute
564
* with that name in no namespace.
567
* @param name the name of the attribute
569
* @return the attribute of this element with the specified name
571
public final Attribute getAttribute(String name) {
572
return getAttribute(name, "");
578
* Returns the attribute with the specified name and namespace URI,
579
* or null if this element does not have an attribute
580
* with that name in that namespace.
583
* @param localName the local name of the attribute
584
* @param namespaceURI the namespace of the attribute
586
* @return the attribute of this element
587
* with the specified name and namespace
589
public final Attribute getAttribute(String localName,
590
String namespaceURI) {
592
if (attributes == null) return null;
593
for (int i = 0; i < numAttributes; i++) {
594
Attribute a = attributes[i];
595
if (a.getLocalName().equals(localName)
596
&& a.getNamespaceURI().equals(namespaceURI)) {
608
* Returns the value of the attribute with the specified
609
* name in no namespace,
610
* or null if this element does not have an attribute
614
* @param name the name of the attribute
616
* @return the value of the attribute of this element
617
* with the specified name
619
public final String getAttributeValue(String name) {
620
return getAttributeValue(name, "");
627
* Returns the number of attributes of this <code>Element</code>,
628
* not counting namespace declarations.
629
* This is always a non-negative number.
632
* @return the number of attributes in the container
634
public final int getAttributeCount() {
635
return numAttributes;
642
* Selects an attribute by index.
643
* The index is purely for convenience and has no particular
644
* meaning. In particular, it is <em>not</em> necessarily the
645
* position of this attribute in the original document from
646
* which this <code>Element</code> object was read.
647
* As with most lists in Java, attributes are numbered
648
* from 0 to one less than the length of the list.
652
* In general, you should not add attributes to or remove
653
* attributes from the list while iterating across it.
654
* Doing so will change the indexes of the other attributes in
655
* the list. it is, however, safe to remove an attribute from
656
* either end of the list (0 or <code>getAttributeCount()-1</code>)
657
* until there are no attributes left.
660
* @param index the attribute to return
662
* @return the index<sup>th</sup> attribute of this element
664
* @throws IndexOutofBoundsException if the index is negative
665
* or greater than or equal to the number of attributes
669
public final Attribute getAttribute(int index) {
671
if (attributes == null) {
672
throw new IndexOutOfBoundsException(
673
"Element does not have any attributes"
676
return attributes[index];
683
* Returns the value of the attribute with the
684
* specified name and namespace URI,
685
* or null if this element does not have such an attribute.
688
* @param localName the name of the attribute
689
* @param namespaceURI the namespace of the attribute
691
* @return the value of the attribute of this element
692
* with the specified name and namespace
694
public final String getAttributeValue(String localName,
695
String namespaceURI) {
697
Attribute attribute = getAttribute(localName, namespaceURI);
698
if (attribute == null) return null;
699
else return attribute.getValue();
706
* Returns the local name of this element, not including the
707
* namespace prefix or colon.
710
* @return the local name of this element
712
public final String getLocalName() {
719
* Returns the complete name of this element, including the
720
* namespace prefix if this element has one.
723
* @return the qualified name of this element
725
public final String getQualifiedName() {
726
if (prefix.length() == 0) return localName;
727
else return prefix + ":" + localName;
733
* Returns the prefix of this element, or the empty string
734
* if this element does not have a prefix.
737
* @return the prefix of this element
739
public final String getNamespacePrefix() {
746
* Returns the namespace URI of this element,
747
* or the empty string if this element is not
751
* @return the namespace URI of this element
753
public final String getNamespaceURI() {
760
* Returns the namespace URI mapped to the specified
761
* prefix within this element. Returns null if this prefix
762
* is not associated with a URI.
765
* @param prefix the namespace prefix whose URI is desired
767
* @return the namespace URI mapped to <code>prefix</code>
769
public final String getNamespaceURI(String prefix) {
771
Element current = this;
772
String result = getLocalNamespaceURI(prefix);
773
while (result == null) {
774
ParentNode parent = current.getParent();
775
if (parent == null || parent.isDocument()) break;
776
current = (Element) parent;
777
result = current.getLocalNamespaceURI(prefix);
779
if (result == null && "".equals(prefix)) result = "";
785
final String getLocalNamespaceURI(String prefix) {
787
if (prefix.equals(this.prefix)) return this.URI;
789
if ("xml".equals(prefix)) {
790
return "http://www.w3.org/XML/1998/namespace";
792
// This next line uses the original Namespaces 1.0
793
// specification rules.
794
// Namespaces 1.0 + errata is different
795
if ("xmlns".equals(prefix)) return "";
796
// Look in the additional namespace declarations
797
if (namespaces != null) {
798
String result = namespaces.getURI(prefix);
799
if (result != null) return result;
801
// Look in the attributes
802
if (prefix.length() != 0 && attributes != null) {
803
for (int i = 0; i < numAttributes; i++) {
804
Attribute a = attributes[i];
805
if (a.getNamespacePrefix().equals(prefix)) {
806
return a.getNamespaceURI();
818
* Sets the local name of this element.
821
* @param localName the new local name
823
* @throws IllegalNameException if <code>localName</code> is not
824
* a legal, non-colonized name
826
public void setLocalName(String localName) {
827
_setLocalName(localName);
831
private void _setLocalName(String localName) {
832
Verifier.checkNCName(localName);
833
this.localName = localName;
839
* Sets the namespace URI of this element.
842
* @param uri the new namespace URI
844
* @throws MalformedURIException if <code>uri</code>
845
* is not an absolute RFC 3986 URI reference
846
* @throws NamespaceException if this element has a prefix
847
* and <code>uri</code> is null or the empty string;
848
* or if the element's prefix is shared by an attribute
849
* or additional namespace
851
public void setNamespaceURI(String uri) {
852
_setNamespaceURI(uri);
856
private void _setNamespaceURI(String uri) {
858
if (uri == null) uri = "";
859
// Next line is needed to avoid unintentional
860
// exceptions below when checking for conflicts
861
if (uri.equals(this.URI)) return;
862
if (uri.length() == 0) { // faster than "".equals(uri)
863
if (prefix.length() != 0) {
864
throw new NamespaceConflictException(
865
"Prefixed elements must have namespace URIs."
869
else Verifier.checkAbsoluteURIReference(uri);
870
// Make sure this doesn't conflict with any local
871
// attribute prefixes or additional namespace declarations
872
// Note that if the prefix equals the prefix, then the
873
// URI must equal the old URI, so the URI can't easily be
874
// changed. (you'd need to detach everything first;
875
// change the URIs, then put it all back together
876
if (namespaces != null) {
877
String result = namespaces.getURI(prefix);
878
if (result != null) {
879
throw new NamespaceConflictException(
880
"new URI conflicts with existing prefix"
884
// Look in the attributes
885
if (uri.length() > 0 && attributes != null) {
886
for (int i = 0; i < numAttributes; i++) {
887
Attribute a = attributes[i];
888
String attPrefix = a.getNamespacePrefix();
889
if (attPrefix.length() == 0) continue;
890
if (a.getNamespacePrefix().equals(prefix)) {
891
throw new NamespaceConflictException(
892
"new element URI " + uri
893
+ " conflicts with attribute "
894
+ a.getQualifiedName()
900
if ("http://www.w3.org/XML/1998/namespace".equals(uri)
901
&& ! "xml".equals(prefix)) {
902
throw new NamespaceConflictException(
903
"Wrong prefix " + prefix +
904
" for the http://www.w3.org/XML/1998/namespace namespace URI"
907
else if ("xml".equals(prefix) &&
908
!"http://www.w3.org/XML/1998/namespace".equals(uri)) {
909
throw new NamespaceConflictException(
910
"Wrong namespace URI " + uri + " for the xml prefix"
921
* Sets the namespace prefix of this element.
922
* You can pass null or the empty string to remove the prefix.
925
* @param prefix the new namespace prefix
927
* @throws IllegalNameException if <code>prefix</code> is
928
* not a legal XML non-colonized name
929
* @throws NamespaceConflictException if <code>prefix</code> is
930
* already in use by an attribute or additional
931
* namespace with a different URI than the element
934
public void setNamespacePrefix(String prefix) {
935
_setNamespacePrefix(prefix);
939
private void _setNamespacePrefix(String prefix) {
941
if (prefix == null) prefix = "";
943
if (prefix.length() != 0) Verifier.checkNCName(prefix);
945
// Check how this affects or conflicts with
946
// attribute namespaces and additional
947
// namespace declarations.
948
String uri = getLocalNamespaceURI(prefix);
950
if (!uri.equals(this.URI) && !"xml".equals(prefix)) {
951
throw new NamespaceConflictException(prefix
952
+ " conflicts with existing prefix");
955
else if ("".equals(this.URI) && !"".equals(prefix)) {
956
throw new NamespaceConflictException(
957
"Cannot assign prefix to element in no namespace");
960
this.prefix = prefix;
967
* Inserts a child node at the specified position.
968
* Inserting at position 0 makes the child the first child
969
* of this node. Inserting at the position
970
* <code>getChildCount()</code>
971
* makes the child the last child of the node.
975
* All the other methods that add a node to the tree,
976
* invoke this method ultimately.
979
* @param position where to insert the child
980
* @param child the node to insert
982
* @throws IllegalAddException if <code>child</code>
983
* is a <code>Document</code>
984
* @throws MultipleParentException if <code>child</code>
985
* already has a parent
986
* @throws NullPointerException if <code>child</code> is null
987
* @throws IndexOutOfBoundsException if the position is negative
988
* or greater than the number of children of this element.
990
void insertionAllowed(Node child, int position) {
993
throw new NullPointerException(
994
"Tried to insert a null child in the tree");
996
else if (child.getParent() != null) {
997
throw new MultipleParentException(child.toString()
998
+ " child already has a parent.");
1000
else if (child.isElement()) {
1001
checkCycle(child, this);
1004
else if (child.isText()
1005
|| child.isProcessingInstruction()
1006
|| child.isComment()) {
1010
throw new IllegalAddException("Cannot add a "
1011
+ child.getClass().getName() + " to an Element.");
1017
private static void checkCycle(Node child, ParentNode parent) {
1019
if (child == parent) {
1020
throw new CycleException("Cannot add a node to itself");
1022
if (child.getChildCount() == 0) return;
1023
while ((parent = parent.getParent()) != null) {
1024
if (parent == child) {
1025
throw new CycleException(
1026
"Cannot add an ancestor as a child");
1035
* Converts a string to a text node and inserts that
1036
* node at the specified position.
1039
* @param position where to insert the child
1040
* @param text the string to convert to a text node and insert
1042
* @throws NullPointerException if text is null
1043
* @throws IndexOutOfBoundsException if the position is negative
1044
* or greater than the number of children of the node
1046
public void insertChild(String text, int position) {
1049
throw new NullPointerException("Inserted null string");
1051
super.fastInsertChild(new Text(text), position);
1058
* Converts a string to a text node
1059
* and appends that node to the children of this node.
1062
* @param text String to add to this node
1064
* @throws IllegalAddException if this node cannot
1065
* have children of this type
1066
* @throws NullPointerException if <code>text</code> is null
1068
public void appendChild(String text) {
1069
insertChild(new Text(text), getChildCount());
1075
* Detaches all children from this node.
1079
* Subclassers should note that the default implementation of this
1080
* method does <strong>not</strong> call <code>removeChild</code>
1081
* or <code>detach</code>. If you override
1082
* <code>removeChild</code>, you'll probably need to override this
1086
* @return a list of all the children removed in the order they
1087
* appeared in the element
1089
public Nodes removeChildren() {
1091
int length = this.getChildCount();
1092
Nodes result = new Nodes();
1093
for (int i = 0; i < length; i++) {
1094
Node child = getChild(i);
1095
if (child.isElement()) fillInBaseURI((Element) child);
1096
child.setParent(null);
1097
result.append(child);
1099
this.children = null;
1100
this.childCount = 0;
1109
* Declares a namespace prefix. This is only necessary when
1110
* prefixes are used in element content and attribute values,
1111
* as in XSLT and the W3C XML Schema Language. Do not use
1112
* this method to declare prefixes for element and attribute
1117
* If you do redeclare a prefix that is already used
1118
* by an element or attribute name, the additional
1119
* namespace is added if and only if the URI is the same.
1120
* Conflicting namespace declarations will throw an exception.
1123
* @param prefix the prefix to declare
1124
* @param uri the absolute URI reference to map the prefix to
1126
* @throws MalformedURIException if <code>URI</code>
1127
* is not an RFC 3986 URI reference
1128
* @throws IllegalNameException if <code>prefix</code> is not
1129
* a legal XML non-colonized name
1130
* @throws NamespaceConflictException if the mapping conflicts
1131
* with an existing element, attribute,
1132
* or additional namespace declaration
1134
public void addNamespaceDeclaration(String prefix, String uri) {
1136
if (prefix == null) prefix = "";
1137
if (uri == null) uri = "";
1139
// check to see if this is the xmlns or xml prefix
1140
if (prefix.equals("xmlns")) {
1141
if (uri.equals("")) {
1142
// This is done automatically
1145
throw new NamespaceConflictException(
1146
"The xmlns prefix cannot bound to any URI");
1148
else if (prefix.equals("xml")) {
1149
if (uri.equals("http://www.w3.org/XML/1998/namespace")) {
1150
// This is done automatically
1153
throw new NamespaceConflictException(
1154
"Wrong namespace URI for xml prefix: " + uri);
1156
else if (uri.equals("http://www.w3.org/XML/1998/namespace")) {
1157
throw new NamespaceConflictException(
1158
"Wrong prefix for http://www.w3.org/XML/1998/namespace namespace: "
1162
if (prefix.length() != 0) {
1163
Verifier.checkNCName(prefix);
1164
Verifier.checkAbsoluteURIReference(uri);
1166
else if (uri.length() != 0) {
1167
// Make sure we're not trying to undeclare
1168
// the default namespace; this is legal.
1169
Verifier.checkAbsoluteURIReference(uri);
1172
String currentBinding = getLocalNamespaceURI(prefix);
1173
if (currentBinding != null && !currentBinding.equals(uri)) {
1176
if (prefix.equals("")) {
1177
message = "Additional namespace " + uri
1178
+ " conflicts with existing default namespace "
1182
message = "Additional namespace " + uri
1183
+ " for the prefix " + prefix
1184
+ " conflicts with existing namespace binding "
1187
throw new NamespaceConflictException(message);
1190
if (namespaces == null) namespaces = new Namespaces();
1191
namespaces.put(prefix, uri);
1198
* Removes the mapping of the specified prefix. This method only
1199
* removes additional namespaces added with
1200
* <code>addNamespaceDeclaration</code>.
1201
* It has no effect on namespaces of elements and attributes.
1202
* If the prefix is not used on this element, this method
1206
* @param prefix the prefix whose declaration should be removed
1208
public void removeNamespaceDeclaration(String prefix) {
1210
if (namespaces != null) {
1211
namespaces.remove(prefix);
1219
* Returns the number of namespace declarations on this
1220
* element. This counts the namespace of the element
1221
* itself (which may be the empty string), the namespace
1222
* of each attribute, and each namespace added
1223
* by <code>addNamespaceDeclaration</code>.
1224
* However, prefixes used multiple times are only counted
1225
* once; and the <code>xml</code> prefix used for
1226
* <code>xml:base</code>, <code>xml:lang</code>, and
1227
* <code>xml:space</code> is not counted even if one of these
1228
* attributes is present on the element.
1232
* The return value is almost always positive. It can be zero
1233
* if and only if the element itself has the prefix
1234
* <code>xml</code>; e.g. <code><xml:space /></code>.
1235
* This is not endorsed by the XML specification. The prefix
1236
* <code>xml</code> is reserved for use by the W3C, which has only
1237
* used it for attributes to date. You really shouldn't do this.
1238
* Nonetheless, this is not malformed so XOM allows it.
1241
* @return the number of namespaces declared by this element
1243
public final int getNamespaceDeclarationCount() {
1245
// This seems to be a hot spot for DOM conversion.
1246
// I'm trying to avoid the overhead of creating and adding
1247
// to a HashSet for the simplest case of an element, none
1248
// of whose attributes are in namespaces, and which has no
1249
// additional namespace declarations. In this case, the
1250
// namespace count is exactly one, which is here indicated
1251
// by a null prefix set.
1252
Set allPrefixes = null;
1253
if (namespaces != null) {
1254
allPrefixes = new HashSet(namespaces.getPrefixes());
1255
allPrefixes.add(prefix);
1257
if ("xml".equals(prefix)) allPrefixes = new HashSet();
1258
// add attribute prefixes
1259
int count = getAttributeCount();
1260
for (int i = 0; i < count; i++) {
1261
Attribute att = getAttribute(i);
1262
String attPrefix = att.getNamespacePrefix();
1263
if (attPrefix.length() != 0 && !"xml".equals(attPrefix)) {
1264
if (allPrefixes == null) {
1265
allPrefixes = new HashSet();
1266
allPrefixes.add(prefix);
1268
allPrefixes.add(attPrefix);
1272
if (allPrefixes == null) return 1;
1273
return allPrefixes.size();
1278
// Used for XPath and serialization
1279
Map getNamespacePrefixesInScope() {
1281
HashMap namespaces = new HashMap();
1283
Element current = this;
1284
while (current != null) {
1286
if (!("xml".equals(current.prefix))) {
1287
addPrefixIfNotAlreadyPresent(namespaces, current, current.prefix);
1290
// add attribute prefixes
1291
int count = current.getAttributeCount();
1292
for (int i = 0; i < count; i++) {
1293
Attribute att = current.getAttribute(i);
1294
String attPrefix = att.getNamespacePrefix();
1295
if (attPrefix.length() != 0 && !("xml".equals(attPrefix))) {
1296
addPrefixIfNotAlreadyPresent(namespaces, current, attPrefix);
1300
// add additional namespace prefixes
1301
// ???? should add more methods to Namespaces to avoid access to private data
1302
if (current.namespaces != null) {
1303
int namespaceCount = current.namespaces.size();
1304
for (int i = 0; i < namespaceCount; i++) {
1305
String nsPrefix = current.namespaces.getPrefix(i);
1306
addPrefixIfNotAlreadyPresent(namespaces, current, nsPrefix);
1310
ParentNode parent = current.getParent();
1311
if (parent == null || parent.isDocument() || parent.isDocumentFragment()) {
1314
current = (Element) parent;
1322
private void addPrefixIfNotAlreadyPresent(HashMap namespaces, Element current, String prefix) {
1323
if (!namespaces.containsKey(prefix)) {
1324
namespaces.put(prefix, current.getLocalNamespaceURI(prefix));
1331
* Returns the index<sup>th</sup> namespace prefix <em>declared</em> on
1332
* this element. Namespaces inherited from ancestors are not included.
1333
* The index is purely for convenience, and has no
1334
* meaning in itself. This includes the namespaces of the element
1335
* name and of all attributes' names (except for those with the
1336
* prefix <code>xml</code> such as <code>xml:space</code>) as well
1337
* as additional declarations made for attribute values and element
1338
* content. However, prefixes used multiple times (e.g. on several
1339
* attribute values) are only reported once. The default
1340
* namespace is reported with an empty string prefix if present.
1341
* Like most lists in Java, the first prefix is at index 0.
1345
* If the namespaces on the element change for any reason
1346
* (adding or removing an attribute in a namespace, adding
1347
* or removing a namespace declaration, changing the prefix
1348
* of an element, etc.) then then this method may skip or
1349
* repeat prefixes. Don't change the prefixes of an element
1350
* while iterating across them.
1353
* @param index the prefix to return
1355
* @return the prefix
1357
* @throws IndexOutOfBoundsException if <code>index</code> is
1358
* negative or greater than or equal to the number of
1359
* namespaces declared by this element.
1362
public final String getNamespacePrefix(int index) {
1364
SortedSet allPrefixes;
1365
if (namespaces != null) {
1366
allPrefixes = new TreeSet(namespaces.getPrefixes());
1368
else allPrefixes = new TreeSet();
1370
if (!("xml".equals(prefix))) allPrefixes.add(prefix);
1372
// add attribute prefixes
1373
for (int i = 0; i < getAttributeCount(); i++) {
1374
Attribute att = getAttribute(i);
1375
String attPrefix = att.getNamespacePrefix();
1376
if (attPrefix.length() != 0 && !("xml".equals(attPrefix))) {
1377
allPrefixes.add(attPrefix);
1381
Iterator iterator = allPrefixes.iterator();
1383
for (int i = 0; i < index; i++) {
1386
return (String) iterator.next();
1388
catch (NoSuchElementException ex) {
1389
throw new IndexOutOfBoundsException(
1390
"No " + index + "th namespace");
1399
* Sets the URI from which this element was loaded,
1400
* and against which relative URLs in this element will be
1401
* resolved, unless an <code>xml:base</code> attribute overrides
1402
* this. Setting the base URI to null or the empty string removes
1403
* any existing base URI.
1406
* @param URI the new base URI for this element
1408
* @throws MalformedURIException if <code>URI</code> is
1409
* not a legal RFC 3986 absolute URI
1411
public void setBaseURI(String URI) {
1412
setActualBaseURI(URI);
1419
* Returns the absolute base URI against which relative URIs in
1420
* this element should be resolved. <code>xml:base</code>
1421
* attributes <em>in the same entity</em> take precedence over the
1422
* actual base URI of the document where the element was found
1423
* or which was set by <code>setBaseURI</code>.
1427
* This URI is made absolute before it is returned
1428
* by resolving the information in this element against the
1429
* information in its parent element and document entity.
1430
* However, it is not always possible to fully absolutize the
1431
* URI in all circumstances. In this case, this method returns the
1432
* empty string to indicate the base URI of the current entity.
1436
* If the element's <code>xml:base</code> attribute contains a
1437
* value that is a syntactically illegal URI (e.g. %GF.html"),
1438
* then the base URI is application dependent. XOM's choice is
1439
* to behave as if the element did not have an <code>xml:base</code>
1443
* @return the base URI of this element
1445
public String getBaseURI() {
1447
String baseURI = "";
1448
String sourceEntity = this.getActualBaseURI();
1450
ParentNode current = this;
1453
String currentEntity = current.getActualBaseURI();
1454
if (sourceEntity.length() != 0
1455
&& ! sourceEntity.equals(currentEntity)) {
1456
baseURI = URIUtil.absolutize(sourceEntity, baseURI);
1460
if (current.isDocument()) {
1461
baseURI = URIUtil.absolutize(currentEntity, baseURI);
1464
Attribute baseAttribute = ((Element) current).getAttribute("base",
1465
"http://www.w3.org/XML/1998/namespace");
1466
if (baseAttribute != null) {
1467
String baseIRI = baseAttribute.getValue();
1468
// The base attribute contains an IRI, not a URI.
1469
// Thus the first thing we have to do is escape it
1470
// to convert illegal characters to hexadecimal escapes.
1471
String base = URIUtil.toURI(baseIRI);
1472
if ("".equals(base)) {
1473
baseURI = getEntityURI();
1476
else if (legalURI(base)) {
1477
if ("".equals(baseURI)) baseURI = base;
1478
else if (URIUtil.isOpaque(base)) break;
1479
else baseURI = URIUtil.absolutize(base, baseURI);
1480
if (URIUtil.isAbsolute(base)) break; // ???? base or baseURI
1483
current = current.getParent();
1484
if (current == null) {
1485
baseURI = URIUtil.absolutize(currentEntity, baseURI);
1490
if (URIUtil.isAbsolute(baseURI)) return baseURI;
1496
private String getEntityURI() {
1498
ParentNode current = this;
1499
while (current != null) {
1500
if (current.actualBaseURI != null
1501
&& current.actualBaseURI.length() != 0) {
1502
return current.actualBaseURI;
1504
current = current.getParent();
1511
private boolean legalURI(String base) {
1514
Verifier.checkURIReference(base);
1517
catch (MalformedURIException ex) {
1526
* Returns a string containing the XML serialization of this
1527
* element. This includes the element and all its attributes
1528
* and descendants. However, it does not contain namespace
1529
* declarations for namespaces inherited from ancestor elements.
1532
* @return the XML representation of this element
1535
public final String toXML() {
1537
StringBuffer result = new StringBuffer(1024);
1538
Node current = this;
1539
boolean endTag = false;
1541
int[] indexes = new int[10];
1547
if (!endTag && current.getChildCount() > 0) {
1548
writeStartTag((Element) current, result);
1549
current = current.getChild(0);
1552
indexes = grow(indexes, top);
1557
writeEndTag((Element) current, result);
1558
if (current == this) break;
1560
else if (current.isElement()) {
1561
writeStartTag((Element) current, result);
1562
if (current == this) break;
1565
result.append(current.toXML());
1568
ParentNode parent = current.getParent();
1569
if (parent.getChildCount() - 1 == index) {
1572
if (current != this) {
1573
index = indexes[top];
1579
indexes[top] = index;
1580
current = parent.getChild(index);
1587
return result.toString();
1592
private static void writeStartTag(Element element, StringBuffer result) {
1595
result.append(element.getQualifiedName());
1597
ParentNode parentNode = element.getParent();
1598
for (int i = 0; i < element.getNamespaceDeclarationCount(); i++) {
1599
String additionalPrefix = element.getNamespacePrefix(i);
1600
String uri = element.getNamespaceURI(additionalPrefix);
1601
if (parentNode != null && parentNode.isElement()) {
1602
Element parentElement = (Element) parentNode;
1604
parentElement.getNamespaceURI(additionalPrefix))) {
1608
else if (uri.length() == 0) {
1609
continue; // no need to say xmlns=""
1612
result.append(" xmlns");
1613
if (additionalPrefix.length() > 0) {
1615
result.append(additionalPrefix);
1617
result.append("=\"");
1623
if (element.attributes != null) {
1624
for (int i = 0; i < element.numAttributes; i++) {
1625
Attribute attribute = element.attributes[i];
1627
result.append(attribute.toXML());
1631
if (element.getChildCount() > 0) {
1635
result.append(" />");
1641
private static void writeEndTag(
1642
Element element, StringBuffer result) {
1644
result.append("</");
1645
result.append(element.getQualifiedName());
1653
* Returns the value of the element as defined by XPath 1.0.
1654
* This is the complete PCDATA content of the element, without
1655
* any tags, comments, or processing instructions after all
1656
* entity and character references have been resolved.
1659
* @return XPath string value of this element
1662
public final String getValue() {
1664
// non-recursive algorithm avoids stack size limitations
1665
int childCount = this.getChildCount();
1666
if (childCount == 0) return "";
1668
Node current = this.getChild(0);
1669
// optimization for common case where element
1670
// has a single text node child
1671
if (childCount == 1 && current.isText()) {
1672
return current.getValue();
1675
StringBuffer result = new StringBuffer();
1677
int[] indexes = new int[10];
1681
boolean endTag = false;
1683
if (!endTag && current.getChildCount() > 0) {
1684
current = current.getChild(0);
1687
indexes = grow(indexes, top);
1691
if (current.isText()) result.append(current.getValue());
1692
ParentNode parent = current.getParent();
1693
if (parent.getChildCount() - 1 == index) {
1696
if (current == this) break;
1697
index = indexes[top];
1702
indexes[top] = index;
1703
current = parent.getChild(index);
1708
return result.toString();
1714
* Creates a deep copy of this element with no parent,
1715
* that can be added to this document or a different one.
1719
* Subclassers should be wary. Implementing this method is trickier
1720
* than it might seem, especially if you wish to avoid potential
1721
* stack overflows in deep documents. In particular, you should not
1722
* rely on the obvious recursive algorithm. Most subclasses should
1723
* override the {@link nu.xom.Element#shallowCopy() shallowCopy}
1727
* @return a deep copy of this element with no parent
1729
public Node copy() {
1730
Element result = copyTag(this);
1731
copyChildren(this, result);
1738
* Creates a very shallow copy of the element with the same name
1739
* and namespace URI, but no children, attributes, base URI, or
1740
* namespace declaration. This method is invoked as necessary
1741
* by the {@link nu.xom.Element#copy() copy} method
1742
* and the {@link nu.xom.Element#Element(nu.xom.Element)
1743
* copy constructor}.
1747
* Subclasses should override this method so that it
1748
* returns an instance of the subclass so that types
1749
* are preserved when copying. This method should not add any
1750
* attributes, namespace declarations, or children to the
1751
* shallow copy. Any such items will be overwritten.
1754
* @return an empty element with the same name and
1755
* namespace as this element
1757
protected Element shallowCopy() {
1758
return new Element(getQualifiedName(), getNamespaceURI());
1764
* Returns a string representation of this element suitable
1765
* for debugging and diagnosis. This is <em>not</em>
1766
* the XML representation of the element.
1769
* @return a non-XML string representation of this element
1771
public final String toString() {
1773
"[" + getClass().getName() + ": " + getQualifiedName() + "]";
1777
boolean isElement() {
1782
private void checkPrefixConflict(Attribute attribute) {
1784
String prefix = attribute.getNamespacePrefix();
1785
String namespaceURI = attribute.getNamespaceURI();
1787
// Look for conflicts
1788
for (int i = 0; i < numAttributes; i++) {
1789
Attribute a = attributes[i];
1790
if (a.getNamespacePrefix().equals(prefix)) {
1791
if (a.getNamespaceURI().equals(namespaceURI)) return;
1792
throw new NamespaceConflictException(
1793
"Prefix of " + attribute.getQualifiedName()
1794
+ " conflicts with " + a.getQualifiedName());
1801
Iterator attributeIterator() {
1803
return new Iterator() {
1805
private int next = 0;
1807
public boolean hasNext() {
1808
return next < numAttributes;
1811
public Object next() throws NoSuchElementException {
1814
Attribute a = attributes[next];
1818
throw new NoSuchElementException("No such attribute");
1822
public void remove() {
1823
throw new UnsupportedOperationException();
b'\\ No newline at end of file'