~ubuntu-branches/ubuntu/precise/xom/precise

« back to all changes in this revision

Viewing changes to src/nu/xom/Element.java

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2007-11-25 15:50:40 UTC
  • Revision ID: james.westby@ubuntu.com-20071125155040-r75ikcqf1vu0cei7
Tags: upstream-1.1
ImportĀ upstreamĀ versionĀ 1.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright 2002-2005 Elliotte Rusty Harold
 
2
   
 
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.
 
6
   
 
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.
 
11
   
 
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
 
16
   
 
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/
 
20
*/
 
21
 
 
22
package nu.xom;
 
23
 
 
24
import java.util.HashMap;
 
25
import java.util.HashSet;
 
26
import java.util.Iterator;
 
27
import java.util.Map;
 
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?
 
31
import java.util.Set;
 
32
import java.util.SortedSet;
 
33
import java.util.TreeSet;
 
34
 
 
35
/**
 
36
 * <p>
 
37
 * This class represents an XML element. Each 
 
38
 * element has the following properties:
 
39
 * </p>
 
40
 * 
 
41
 * <ul>
 
42
 *   <li>Local name</li>
 
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>
 
49
 * </ul>
 
50
 * 
 
51
 * @author Elliotte Rusty Harold
 
52
 * @version 1.1b3
 
53
 *
 
54
 */
 
55
public class Element extends ParentNode {
 
56
 
 
57
    private String localName;
 
58
    private String prefix;
 
59
    private String URI;
 
60
 
 
61
    private Attribute[] attributes = null;
 
62
    private int         numAttributes = 0;
 
63
    private Namespaces  namespaces = null;
 
64
 
 
65
    /**
 
66
     * <p>
 
67
     * Creates a new element in no namespace.
 
68
     * </p>
 
69
     * 
 
70
     * @param name the name of the element
 
71
     * 
 
72
     * @throws IllegalNameException if <code>name</code>
 
73
     *     is not a legal XML 1.0 non-colonized name
 
74
     */
 
75
    public Element(String name) {
 
76
        this(name, "");
 
77
    }
 
78
 
 
79
    
 
80
    /**
 
81
     * <p>
 
82
     * Creates a new element in a namespace.
 
83
     * </p>
 
84
     * 
 
85
     * @param name the qualified name of the element
 
86
     * @param uri the namespace URI of the element
 
87
     * 
 
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
 
94
     */
 
95
    public Element(String name, String uri) {
 
96
        
 
97
        // The shadowing is important here.
 
98
        // I don't want to set the prefix field just yet.
 
99
        String prefix = "";
 
100
        String localName = name;
 
101
        int colon = name.indexOf(':');
 
102
        if (colon > 0) {
 
103
            prefix = name.substring(0, colon);   
 
104
            localName = name.substring(colon + 1);   
 
105
        }
 
106
        
 
107
        // The order of these next two calls
 
108
        // matters a great deal.
 
109
        _setNamespacePrefix(prefix);
 
110
        _setNamespaceURI(uri);
 
111
        try {
 
112
            _setLocalName(localName);
 
113
        }
 
114
        catch (IllegalNameException ex) {
 
115
            ex.setData(name);
 
116
            throw ex;
 
117
        }
 
118
        
 
119
    }
 
120
 
 
121
    
 
122
    private Element() {}
 
123
 
 
124
    
 
125
    static Element build(String name, String uri, String localName) {
 
126
        
 
127
        Element result = new Element();
 
128
        String prefix = "";
 
129
        int colon = name.indexOf(':');
 
130
        if (colon >= 0) {
 
131
            prefix = name.substring(0, colon);  
 
132
        }
 
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 
 
139
        // no conflicts.
 
140
        if (! "".equals(uri)) Verifier.checkAbsoluteURIReference(uri);
 
141
        result.URI = uri;
 
142
        return result;
 
143
        
 
144
    }
 
145
 
 
146
 
 
147
    /**
 
148
     * <p>
 
149
     * Creates a deep copy of an element.
 
150
     * The copy is disconnected from the tree, and does not
 
151
     * have a parent.
 
152
     * </p>
 
153
     * 
 
154
     * @param element the element to copy
 
155
     * 
 
156
     */
 
157
    public Element(Element element) {
 
158
        
 
159
        this.prefix = element.prefix;
 
160
        this.localName = element.localName;
 
161
        this.URI = element.URI;
 
162
        
 
163
        // Attach additional namespaces
 
164
        if (element.namespaces != null) {
 
165
            this.namespaces = element.namespaces.copy();
 
166
        }
 
167
        
 
168
        // Attach clones of attributes
 
169
        if (element.attributes != null) {
 
170
            this.attributes = element.copyAttributes(this);
 
171
            this.numAttributes = element.numAttributes;
 
172
        } 
 
173
        
 
174
        this.actualBaseURI = element.findActualBaseURI();
 
175
        
 
176
        copyChildren(element, this);
 
177
        
 
178
    }
 
179
    
 
180
 
 
181
    private Attribute[] copyAttributes(Element newParent) {
 
182
 
 
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);
 
187
        }
 
188
        return copy;
 
189
        
 
190
    }
 
191
    
 
192
    
 
193
    private static Element copyTag(final Element source) {
 
194
        
 
195
        Element result = source.shallowCopy();
 
196
        
 
197
        // Attach additional namespaces
 
198
        if (source.namespaces != null) {
 
199
            result.namespaces = source.namespaces.copy();
 
200
        }
 
201
        
 
202
        // Attach clones of attributes
 
203
        if (source.attributes != null) {
 
204
            result.attributes = source.copyAttributes(result);
 
205
            result.numAttributes = source.numAttributes;
 
206
        } 
 
207
        
 
208
        result.actualBaseURI = source.findActualBaseURI();
 
209
        
 
210
        return result;
 
211
        
 
212
    }
 
213
 
 
214
 
 
215
    private static void copyChildren(final Element sourceElement, 
 
216
      Element resultElement) {
 
217
        
 
218
        if (sourceElement.getChildCount() == 0) return;
 
219
        ParentNode resultParent = resultElement;
 
220
        Node sourceCurrent = sourceElement;
 
221
        int index = 0;
 
222
        int[] indexes = new int[10];
 
223
        int top = 0;
 
224
        indexes[0] = 0;
 
225
        
 
226
        // true if processing the element for the 2nd time; 
 
227
        // i.e. the element's end-tag
 
228
        boolean endTag = false; 
 
229
        
 
230
        while (true) {
 
231
            if (!endTag && sourceCurrent.getChildCount() > 0) {
 
232
               sourceCurrent = sourceCurrent.getChild(0);
 
233
               index = 0;
 
234
               top++;
 
235
               indexes = grow(indexes, top);
 
236
               indexes[top] = 0;
 
237
            }
 
238
            else {
 
239
                endTag = false;
 
240
                ParentNode sourceParent = sourceCurrent.getParent(); 
 
241
                if (sourceParent.getChildCount() - 1 == index) {
 
242
                    sourceCurrent = sourceParent; 
 
243
                    top--;
 
244
                    if (sourceCurrent == sourceElement) break;
 
245
                    // switch parent up
 
246
                    resultParent = (Element) resultParent.getParent();
 
247
                    index = indexes[top];
 
248
                    endTag = true;
 
249
                    continue;
 
250
                }
 
251
                else {
 
252
                    index++;
 
253
                    indexes[top] = index;
 
254
                    sourceCurrent = sourceParent.getChild(index); 
 
255
                }
 
256
            }
 
257
            
 
258
            if (sourceCurrent.isElement()) {
 
259
                Element child = copyTag((Element) sourceCurrent);
 
260
                resultParent.appendChild(child); 
 
261
                if (sourceCurrent.getChildCount() > 0) { 
 
262
                    resultParent = child;
 
263
                }
 
264
            }
 
265
            else {
 
266
                Node child = sourceCurrent.copy();
 
267
                resultParent.appendChild(child);
 
268
            }
 
269
            
 
270
        } 
 
271
        
 
272
    }
 
273
 
 
274
 
 
275
    private static int[] grow(int[] indexes, int top) {
 
276
        
 
277
        if (top < indexes.length) return indexes;
 
278
        int[] result = new int[indexes.length*2];
 
279
        System.arraycopy(indexes, 0, result, 0, indexes.length);
 
280
        return result;
 
281
        
 
282
    }
 
283
 
 
284
 
 
285
    /**
 
286
     * <p>
 
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.
 
290
     * </p>
 
291
     * 
 
292
     * @param name the name of the elements included in the list
 
293
     * 
 
294
     * @return a comatose list containing the child elements of this 
 
295
     *     element with the specified name
 
296
     */
 
297
    public final Elements getChildElements(String name) {
 
298
        return getChildElements(name, "");
 
299
    }
 
300
 
 
301
    
 
302
    /**
 
303
     * <p>
 
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.
 
311
     * </p>
 
312
     * 
 
313
     * @param localName the name of the elements included in the list
 
314
     * @param namespaceURI the namespace URI of the elements included
 
315
     *     in the list
 
316
     * 
 
317
     * @return a comatose list containing the child  
 
318
     *    elements of this element with the specified
 
319
     *    name in the specified namespace
 
320
     */
 
321
    public final Elements getChildElements(String localName, 
 
322
     String namespaceURI) {
 
323
        
 
324
        if (namespaceURI == null) namespaceURI = "";
 
325
        if (localName == null) localName = "";
 
326
        
 
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);
 
336
                }
 
337
            }
 
338
        }
 
339
        return elements;    
 
340
        
 
341
    }
 
342
 
 
343
    
 
344
    /**
 
345
     * <p>
 
346
     * Returns a list of all the child elements 
 
347
     * of this element in document order.
 
348
     * </p>
 
349
     * 
 
350
     * @return a comatose list containing all  
 
351
     *    child elements of this element
 
352
     */
 
353
    public final Elements getChildElements() {
 
354
        
 
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);  
 
361
            }
 
362
        }
 
363
        return elements;    
 
364
        
 
365
    }
 
366
 
 
367
    
 
368
    /**
 
369
     * <p>
 
370
     * Returns the first child
 
371
     * element with the specified name in no namespace.
 
372
     * If there is no such element, it returns null.
 
373
     * </p>
 
374
     * 
 
375
     * @param name the name of the element to return
 
376
     * 
 
377
     * @return the first child element with the specified local name 
 
378
     *    in no namespace or null if there is no such element
 
379
     */
 
380
    public final Element getFirstChildElement(String name) {
 
381
        return getFirstChildElement(name, "");
 
382
    }
 
383
 
 
384
    
 
385
    /**
 
386
     * <p>
 
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.
 
390
     * </p>
 
391
     * 
 
392
     * @param localName the local name of the element to return
 
393
     * @param namespaceURI the namespace URI of the element to return
 
394
     * 
 
395
     * @return the first child with the specified local name in the
 
396
     *      specified namespace, or null if there is no such element
 
397
     */
 
398
    public final Element getFirstChildElement(String localName, 
 
399
     String namespaceURI) {
 
400
        
 
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())) {
 
407
                    return element;
 
408
                }   
 
409
            }
 
410
        }
 
411
        return null;    
 
412
        
 
413
    }
 
414
 
 
415
 
 
416
    /**
 
417
     * <p>
 
418
     * Adds an attribute to this element, replacing any existing 
 
419
     * attribute with the same local name and namespace URI.
 
420
     * </p>
 
421
     * 
 
422
     * @param attribute the attribute to add
 
423
     * 
 
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
 
430
     *      of this element
 
431
     */
 
432
    public void addAttribute(Attribute attribute) {
 
433
 
 
434
        if (attribute.getParent() != null) {
 
435
            throw new MultipleParentException(
 
436
              "Attribute already has a parent");
 
437
        }
 
438
        
 
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);  
 
448
            }
 
449
            // check for conflicts with additional namespaces
 
450
            if (namespaces != null) {
 
451
                String existing 
 
452
                 = namespaces.getURI(attribute.getNamespacePrefix());
 
453
                if (existing != null 
 
454
                  && !existing.equals(attribute.getNamespaceURI())) {
 
455
                    throw new NamespaceConflictException("Attribute prefix  " 
 
456
                      + attPrefix 
 
457
                      + " conflicts with namespace declaration.");            
 
458
                }
 
459
            }
 
460
            
 
461
        }
 
462
        
 
463
        if (attributes == null) attributes = new Attribute[1];
 
464
        checkPrefixConflict(attribute);
 
465
        
 
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);
 
471
        
 
472
        add(attribute);
 
473
        attribute.setParent(this);
 
474
        
 
475
    }
 
476
    
 
477
    
 
478
    private void add(Attribute attribute) {
 
479
 
 
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;
 
484
        }
 
485
        attributes[numAttributes] = attribute;
 
486
        numAttributes++;
 
487
        
 
488
    }
 
489
 
 
490
 
 
491
    private boolean remove(Attribute attribute) {
 
492
 
 
493
        int index = -1;
 
494
        for (int i = 0; i < attributes.length; i++) {
 
495
            if (attributes[i] == attribute) {
 
496
                index = i;
 
497
                break;
 
498
            }
 
499
        }
 
500
        
 
501
        if (index == -1) return false;
 
502
        
 
503
        int toCopy = numAttributes-index-1;
 
504
        if (toCopy > 0) {
 
505
            System.arraycopy(attributes, index+1, attributes, index, toCopy);
 
506
        }
 
507
        numAttributes--;
 
508
        attributes[numAttributes] = null;
 
509
        return true;
 
510
        
 
511
    }
 
512
 
 
513
 
 
514
    void fastAddAttribute(Attribute attribute) {
 
515
        if (attributes == null) attributes = new Attribute[1];
 
516
        add(attribute);
 
517
        attribute.setParent(this);
 
518
    }
 
519
 
 
520
 
 
521
    /**
 
522
     * <p>
 
523
     * Removes an attribute from this element.
 
524
     * </p>
 
525
     * 
 
526
     * @param attribute the attribute to remove
 
527
     * 
 
528
     * @return the attribute that was removed
 
529
     * 
 
530
     * @throws NoSuchAttributeException if this element is not the  
 
531
     *     parent of attribute
 
532
     * 
 
533
     */
 
534
    public Attribute removeAttribute(Attribute attribute) {
 
535
        
 
536
        if (attributes == null) {
 
537
            throw new NoSuchAttributeException(
 
538
              "Tried to remove attribute "
 
539
              + attribute.getQualifiedName() 
 
540
              + " from non-parent element");
 
541
        }        
 
542
        if (attribute == null) {
 
543
            throw new NullPointerException(
 
544
              "Tried to remove null attribute");
 
545
        }        
 
546
        if (remove(attribute)) {
 
547
            attribute.setParent(null);
 
548
            return attribute;
 
549
        }
 
550
        else {
 
551
            throw new NoSuchAttributeException(
 
552
              "Tried to remove attribute "
 
553
              + attribute.getQualifiedName() 
 
554
              + " from non-parent element");            
 
555
        }
 
556
        
 
557
    }
 
558
 
 
559
 
 
560
    /**
 
561
     * <p>
 
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.
 
565
     * </p>
 
566
     * 
 
567
     * @param name the name of the attribute
 
568
     * 
 
569
     * @return the attribute of this element with the specified name
 
570
     */
 
571
    public final Attribute getAttribute(String name) {
 
572
        return getAttribute(name, "");
 
573
    }
 
574
 
 
575
    
 
576
    /**
 
577
     * <p>
 
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.
 
581
     * </p>
 
582
     * 
 
583
     * @param localName the local name of the attribute
 
584
     * @param namespaceURI the namespace of the attribute
 
585
     * 
 
586
     * @return the attribute of this element 
 
587
     *     with the specified name and namespace
 
588
     */
 
589
    public final Attribute getAttribute(String localName,
 
590
      String namespaceURI) {
 
591
        
 
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)) {
 
597
                return a;
 
598
            }   
 
599
        }
 
600
        
 
601
        return null;
 
602
        
 
603
    }
 
604
 
 
605
    
 
606
    /**
 
607
     * <p>
 
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 
 
611
     * with that name.
 
612
     * </p>
 
613
     * 
 
614
     * @param name the name of the attribute
 
615
     * 
 
616
     * @return the value of the attribute of this element 
 
617
     *     with the specified name
 
618
     */
 
619
    public final String getAttributeValue(String name) {
 
620
        return getAttributeValue(name, "");
 
621
    } 
 
622
 
 
623
    
 
624
    /**
 
625
     * 
 
626
     * <p>
 
627
     * Returns the number of attributes of this <code>Element</code>,
 
628
     * not counting namespace declarations.
 
629
     * This is always a non-negative number.
 
630
     * </p>
 
631
     * 
 
632
     * @return the number of attributes in the container
 
633
     */
 
634
    public final int getAttributeCount() {
 
635
        return numAttributes;   
 
636
    }
 
637
    
 
638
    
 
639
    /**
 
640
     * 
 
641
     * <p>
 
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.
 
649
     * </p>
 
650
     * 
 
651
     * <p>
 
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. 
 
658
     * </p>
 
659
     * 
 
660
     * @param index the attribute to return
 
661
     * 
 
662
     * @return the index<sup>th</sup> attribute of this element
 
663
     * 
 
664
     * @throws IndexOutofBoundsException if the index is negative 
 
665
     *   or greater than or equal to the number of attributes 
 
666
     *   of this element
 
667
     * 
 
668
     */
 
669
    public final Attribute getAttribute(int index) {
 
670
        
 
671
        if (attributes == null) {
 
672
            throw new IndexOutOfBoundsException(
 
673
              "Element does not have any attributes"
 
674
            );
 
675
        }
 
676
        return attributes[index]; 
 
677
        
 
678
    }
 
679
 
 
680
 
 
681
    /**
 
682
     * <p>
 
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.
 
686
     * </p>
 
687
     * 
 
688
     * @param localName the name of the attribute
 
689
     * @param namespaceURI the namespace of the attribute
 
690
     * 
 
691
     * @return the value of the attribute of this element 
 
692
     *     with the specified name and namespace
 
693
     */
 
694
    public final String getAttributeValue(String localName, 
 
695
                                          String namespaceURI) {
 
696
        
 
697
        Attribute attribute = getAttribute(localName, namespaceURI);
 
698
        if (attribute == null) return null;
 
699
        else return attribute.getValue();
 
700
        
 
701
    }
 
702
    
 
703
    
 
704
    /**
 
705
     * <p>
 
706
     * Returns the local name of this element, not including the 
 
707
     * namespace prefix or colon.
 
708
     * </p>
 
709
     * 
 
710
     * @return the local name of this element
 
711
     */
 
712
    public final String getLocalName() {
 
713
        return localName;
 
714
    }
 
715
 
 
716
    
 
717
    /**
 
718
     * <p>
 
719
     * Returns the complete name of this element, including the 
 
720
     * namespace prefix if this element has one.
 
721
     * </p>
 
722
     * 
 
723
     * @return the qualified name of this element
 
724
     */
 
725
    public final String getQualifiedName() {
 
726
        if (prefix.length() == 0) return localName;
 
727
        else return prefix + ":" + localName;
 
728
    }
 
729
 
 
730
    
 
731
    /**
 
732
     * <p>
 
733
     * Returns the prefix of this element, or the empty string
 
734
     * if this element does not have a prefix.
 
735
     * </p>
 
736
     * 
 
737
     * @return the prefix of this element
 
738
     */
 
739
    public final String getNamespacePrefix() {
 
740
        return prefix;
 
741
    }
 
742
 
 
743
    
 
744
    /**
 
745
     * <p>
 
746
     * Returns the namespace URI of this element,
 
747
     * or the empty string if this element is not
 
748
     * in a namespace.
 
749
     * </p>
 
750
     * 
 
751
     * @return  the namespace URI of this element
 
752
     */
 
753
    public final String getNamespaceURI() {
 
754
        return URI;
 
755
    }
 
756
 
 
757
    
 
758
    /**
 
759
     * <p>
 
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.
 
763
     * </p>
 
764
     * 
 
765
     * @param prefix the namespace prefix whose URI is desired
 
766
     *
 
767
     * @return the namespace URI mapped to <code>prefix</code>
 
768
     */
 
769
    public final String getNamespaceURI(String prefix) {
 
770
        
 
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);
 
778
        }
 
779
        if (result == null && "".equals(prefix)) result = "";
 
780
        return result;
 
781
 
 
782
    }
 
783
 
 
784
    
 
785
    final String getLocalNamespaceURI(String prefix) {
 
786
        
 
787
        if (prefix.equals(this.prefix)) return this.URI;
 
788
        
 
789
        if ("xml".equals(prefix)) {
 
790
            return "http://www.w3.org/XML/1998/namespace";
 
791
        }
 
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;
 
800
        }
 
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();
 
807
                }   
 
808
            }
 
809
        }       
 
810
        
 
811
        return null;
 
812
        
 
813
    }
 
814
 
 
815
   
 
816
    /**
 
817
     * <p>
 
818
     * Sets the local name of this element.
 
819
     * </p>
 
820
     * 
 
821
     * @param localName the new local name
 
822
     * 
 
823
     * @throws IllegalNameException if <code>localName</code> is not
 
824
     *     a legal, non-colonized name
 
825
     */
 
826
    public void setLocalName(String localName) {       
 
827
        _setLocalName(localName);
 
828
    }
 
829
 
 
830
 
 
831
    private void _setLocalName(String localName) {
 
832
        Verifier.checkNCName(localName);
 
833
        this.localName = localName;
 
834
    }
 
835
 
 
836
 
 
837
    /**
 
838
     * <p>
 
839
     * Sets the namespace URI of this element.
 
840
     * </p>
 
841
     * 
 
842
     * @param uri the new namespace URI
 
843
     * 
 
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
 
850
     */
 
851
    public void setNamespaceURI(String uri) {
 
852
        _setNamespaceURI(uri);
 
853
    }
 
854
 
 
855
    
 
856
    private void _setNamespaceURI(String uri) {
 
857
        
 
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."
 
866
                );  
 
867
            }
 
868
        }
 
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"
 
881
                );
 
882
            }
 
883
        }
 
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()
 
895
                    );
 
896
                }   
 
897
            } 
 
898
        }      
 
899
 
 
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"
 
905
            );      
 
906
        }
 
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"
 
911
            );      
 
912
        }
 
913
        
 
914
        this.URI = uri;
 
915
        
 
916
    }
 
917
 
 
918
 
 
919
    /**
 
920
     * <p>
 
921
     * Sets the namespace prefix of this element.
 
922
     * You can pass null or the empty string to remove the prefix.
 
923
     * </p>
 
924
     * 
 
925
     * @param prefix the new namespace prefix
 
926
     * 
 
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
 
932
     *     itself
 
933
     */
 
934
    public void setNamespacePrefix(String prefix) {
 
935
        _setNamespacePrefix(prefix);
 
936
    }
 
937
 
 
938
 
 
939
    private void _setNamespacePrefix(String prefix) {
 
940
        
 
941
        if (prefix == null) prefix = "";
 
942
            
 
943
        if (prefix.length() != 0) Verifier.checkNCName(prefix);
 
944
 
 
945
        // Check how this affects or conflicts with
 
946
        // attribute namespaces and additional
 
947
        // namespace declarations.
 
948
        String uri = getLocalNamespaceURI(prefix);
 
949
        if (uri != null) {
 
950
            if (!uri.equals(this.URI) && !"xml".equals(prefix)) {
 
951
                throw new NamespaceConflictException(prefix 
 
952
                 + " conflicts with existing prefix");
 
953
            }
 
954
        }
 
955
        else if ("".equals(this.URI) && !"".equals(prefix)) {
 
956
            throw new NamespaceConflictException(
 
957
              "Cannot assign prefix to element in no namespace");            
 
958
        } 
 
959
 
 
960
        this.prefix = prefix;
 
961
        
 
962
    }
 
963
 
 
964
 
 
965
    /**
 
966
     * <p>
 
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.
 
972
     * </p>
 
973
     * 
 
974
     * <p>
 
975
     * All the other methods that add a node to the tree,
 
976
     * invoke this method ultimately.
 
977
     * </p>
 
978
     * 
 
979
     * @param position where to insert the child
 
980
     * @param child the node to insert
 
981
     * 
 
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.
 
989
     */
 
990
    void insertionAllowed(Node child, int position) {
 
991
        
 
992
        if (child == null) {
 
993
            throw new NullPointerException(
 
994
             "Tried to insert a null child in the tree");
 
995
        }
 
996
        else if (child.getParent() != null) {
 
997
            throw new MultipleParentException(child.toString() 
 
998
              + " child already has a parent.");
 
999
        }
 
1000
        else if (child.isElement()) {
 
1001
            checkCycle(child, this);
 
1002
            return;            
 
1003
        }
 
1004
        else if (child.isText()
 
1005
          || child.isProcessingInstruction()
 
1006
          || child.isComment()) {
 
1007
            return;
 
1008
        }
 
1009
        else {
 
1010
            throw new IllegalAddException("Cannot add a "
 
1011
             + child.getClass().getName() + " to an Element.");
 
1012
        }
 
1013
 
 
1014
    }
 
1015
    
 
1016
    
 
1017
    private static void checkCycle(Node child, ParentNode parent) {       
 
1018
        
 
1019
        if (child == parent) {
 
1020
            throw new CycleException("Cannot add a node to itself");
 
1021
        }
 
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");
 
1027
            }
 
1028
        }
 
1029
        
 
1030
    }
 
1031
 
 
1032
    
 
1033
    /**
 
1034
     * <p>
 
1035
     * Converts a string to a text node and inserts that
 
1036
     * node at the specified position.
 
1037
     * </p>
 
1038
     * 
 
1039
     * @param position where to insert the child
 
1040
     * @param text the string to convert to a text node and insert
 
1041
     * 
 
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
 
1045
     */
 
1046
    public void insertChild(String text, int position) {
 
1047
        
 
1048
       if (text == null) {
 
1049
           throw new NullPointerException("Inserted null string");
 
1050
       }
 
1051
       super.fastInsertChild(new Text(text), position);
 
1052
       
 
1053
    } 
 
1054
 
 
1055
 
 
1056
    /**
 
1057
     * <p>
 
1058
     * Converts a string to a text node
 
1059
     * and appends that node to the children of this node.
 
1060
     * </p>
 
1061
     * 
 
1062
     * @param text String to add to this node
 
1063
     * 
 
1064
     * @throws IllegalAddException if this node cannot 
 
1065
     *     have children of this type
 
1066
     * @throws NullPointerException if <code>text</code> is null
 
1067
     */
 
1068
    public void appendChild(String text) {
 
1069
        insertChild(new Text(text), getChildCount());
 
1070
    } 
 
1071
 
 
1072
 
 
1073
    /**
 
1074
     * <p>
 
1075
     * Detaches all children from this node. 
 
1076
     * </p>
 
1077
     * 
 
1078
     * <p>
 
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
 
1083
     * method as well.
 
1084
     * </p>
 
1085
     * 
 
1086
     * @return a list of all the children removed in the order they
 
1087
     *     appeared in the element
 
1088
     */
 
1089
    public Nodes removeChildren() {
 
1090
        
 
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);
 
1098
        }   
 
1099
        this.children = null;
 
1100
        this.childCount = 0;
 
1101
        
 
1102
        return result;
 
1103
        
 
1104
    }
 
1105
 
 
1106
    
 
1107
    /**
 
1108
     * <p>
 
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 
 
1113
     * names.
 
1114
     * </p>
 
1115
     * 
 
1116
     * <p>
 
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.
 
1121
     * </p>
 
1122
     * 
 
1123
     * @param prefix the prefix to declare
 
1124
     * @param uri the absolute URI reference to map the prefix to
 
1125
     * 
 
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
 
1133
     */
 
1134
    public void addNamespaceDeclaration(String prefix, String uri) {
 
1135
 
 
1136
        if (prefix == null) prefix = "";
 
1137
        if (uri == null) uri = "";
 
1138
        
 
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
 
1143
                return; 
 
1144
            }
 
1145
            throw new NamespaceConflictException(
 
1146
             "The xmlns prefix cannot bound to any URI");   
 
1147
        }
 
1148
        else if (prefix.equals("xml")) {
 
1149
            if (uri.equals("http://www.w3.org/XML/1998/namespace")) {
 
1150
                // This is done automatically
 
1151
                return; 
 
1152
            }
 
1153
            throw new NamespaceConflictException(
 
1154
              "Wrong namespace URI for xml prefix: " + uri);
 
1155
        }
 
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: " 
 
1159
               + prefix);  
 
1160
        }
 
1161
        
 
1162
        if (prefix.length() != 0) {
 
1163
            Verifier.checkNCName(prefix);
 
1164
            Verifier.checkAbsoluteURIReference(uri);
 
1165
        }
 
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);
 
1170
        }
 
1171
        
 
1172
        String currentBinding = getLocalNamespaceURI(prefix);
 
1173
        if (currentBinding != null && !currentBinding.equals(uri)) {
 
1174
            
 
1175
            String message;
 
1176
            if (prefix.equals("")) {
 
1177
                message =  "Additional namespace " + uri 
 
1178
                  + " conflicts with existing default namespace "
 
1179
                  + currentBinding;
 
1180
            }
 
1181
            else {
 
1182
                message = "Additional namespace " + uri 
 
1183
                  + " for the prefix " + prefix 
 
1184
                  + " conflicts with existing namespace binding "
 
1185
                  + currentBinding;
 
1186
            }
 
1187
            throw new NamespaceConflictException(message);   
 
1188
        }
 
1189
        
 
1190
        if (namespaces == null) namespaces = new Namespaces();
 
1191
        namespaces.put(prefix, uri);
 
1192
        
 
1193
    }
 
1194
 
 
1195
    
 
1196
    /**
 
1197
     * <p>
 
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
 
1203
     * does nothing.
 
1204
     * </p>
 
1205
     * 
 
1206
     * @param prefix the prefix whose declaration should be removed
 
1207
     */
 
1208
    public void removeNamespaceDeclaration(String prefix) {
 
1209
 
 
1210
        if (namespaces != null) {
 
1211
            namespaces.remove(prefix);
 
1212
        }
 
1213
        
 
1214
    }
 
1215
 
 
1216
    
 
1217
    /**
 
1218
     * <p>
 
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.
 
1229
     * </p>
 
1230
     * 
 
1231
     * <p>
 
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>&lt;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.
 
1239
     * </p>
 
1240
     * 
 
1241
     * @return the number of namespaces declared by this element
 
1242
     */
 
1243
    public final int getNamespaceDeclarationCount() {
 
1244
        
 
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);
 
1256
        } 
 
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);
 
1267
                }
 
1268
                allPrefixes.add(attPrefix);    
 
1269
            }
 
1270
        }
 
1271
        
 
1272
        if (allPrefixes == null) return 1; 
 
1273
        return allPrefixes.size();
 
1274
        
 
1275
    }
 
1276
    
 
1277
    
 
1278
    // Used for XPath and serialization
 
1279
    Map getNamespacePrefixesInScope() {
 
1280
        
 
1281
        HashMap namespaces = new HashMap();
 
1282
        
 
1283
        Element current = this;
 
1284
        while (current != null) {
 
1285
            
 
1286
            if (!("xml".equals(current.prefix))) {
 
1287
                addPrefixIfNotAlreadyPresent(namespaces, current, current.prefix);
 
1288
            }
 
1289
            
 
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);
 
1297
                }
 
1298
            }
 
1299
            
 
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);
 
1307
                }
 
1308
            }
 
1309
            
 
1310
            ParentNode parent = current.getParent();
 
1311
            if (parent == null || parent.isDocument() || parent.isDocumentFragment()) {
 
1312
                break;
 
1313
            }
 
1314
            current = (Element) parent;
 
1315
        }
 
1316
 
 
1317
        return namespaces;
 
1318
        
 
1319
    }
 
1320
 
 
1321
 
 
1322
    private void addPrefixIfNotAlreadyPresent(HashMap namespaces, Element current, String prefix) {
 
1323
        if (!namespaces.containsKey(prefix)) {
 
1324
            namespaces.put(prefix, current.getLocalNamespaceURI(prefix));
 
1325
        }
 
1326
    }
 
1327
 
 
1328
    
 
1329
    /**
 
1330
     * <p>
 
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.
 
1342
     * </p>
 
1343
     * 
 
1344
     * <p>
 
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. 
 
1351
     * </p>
 
1352
     * 
 
1353
     * @param index the prefix to return
 
1354
     * 
 
1355
     * @return the prefix
 
1356
     * 
 
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.
 
1360
     * 
 
1361
     */
 
1362
    public final String getNamespacePrefix(int index) {
 
1363
        
 
1364
        SortedSet allPrefixes;
 
1365
        if (namespaces != null) {
 
1366
            allPrefixes = new TreeSet(namespaces.getPrefixes());
 
1367
        } 
 
1368
        else allPrefixes = new TreeSet();
 
1369
        
 
1370
        if (!("xml".equals(prefix))) allPrefixes.add(prefix);
 
1371
        
 
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);    
 
1378
            }
 
1379
        }
 
1380
        
 
1381
        Iterator iterator = allPrefixes.iterator();
 
1382
        try {
 
1383
            for (int i = 0; i < index; i++) {
 
1384
                iterator.next();   
 
1385
            }
 
1386
            return (String) iterator.next();
 
1387
        }
 
1388
        catch (NoSuchElementException ex) {
 
1389
            throw new IndexOutOfBoundsException(
 
1390
              "No " + index + "th namespace");   
 
1391
        }
 
1392
        
 
1393
    }    
 
1394
 
 
1395
    
 
1396
    /**
 
1397
     * 
 
1398
     * <p>
 
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.
 
1404
     * </p>
 
1405
     * 
 
1406
     * @param URI the new base URI for this element
 
1407
     * 
 
1408
     * @throws MalformedURIException if <code>URI</code> is 
 
1409
     *     not a legal RFC 3986 absolute URI
 
1410
     */
 
1411
    public void setBaseURI(String URI) { 
 
1412
        setActualBaseURI(URI);       
 
1413
    }
 
1414
    
 
1415
    
 
1416
    /**
 
1417
     * 
 
1418
     * <p>
 
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>.
 
1424
     * </p>
 
1425
     * 
 
1426
     * <p>
 
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.
 
1433
     * </p>
 
1434
     * 
 
1435
     * <p>
 
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>
 
1440
     * attribute. 
 
1441
     * </p>
 
1442
     * 
 
1443
     * @return the base URI of this element 
 
1444
     */
 
1445
     public String getBaseURI() {
 
1446
 
 
1447
        String baseURI = "";
 
1448
        String sourceEntity = this.getActualBaseURI();
 
1449
        
 
1450
        ParentNode current = this;
 
1451
        
 
1452
        while (true) {
 
1453
            String currentEntity = current.getActualBaseURI();
 
1454
            if (sourceEntity.length() != 0 
 
1455
              && ! sourceEntity.equals(currentEntity)) {
 
1456
                baseURI = URIUtil.absolutize(sourceEntity, baseURI);
 
1457
                break;
 
1458
            }
 
1459
            
 
1460
            if (current.isDocument()) {
 
1461
                baseURI = URIUtil.absolutize(currentEntity, baseURI);
 
1462
                break;
 
1463
            }
 
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();
 
1474
                    break;
 
1475
                }
 
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
 
1481
                }
 
1482
            }
 
1483
            current = current.getParent();
 
1484
            if (current == null) {
 
1485
                baseURI = URIUtil.absolutize(currentEntity, baseURI);
 
1486
                break;
 
1487
            }
 
1488
        }
 
1489
        
 
1490
        if (URIUtil.isAbsolute(baseURI)) return baseURI;
 
1491
        return "";
 
1492
            
 
1493
    }
 
1494
    
 
1495
    
 
1496
    private String getEntityURI() {
 
1497
     
 
1498
        ParentNode current = this;
 
1499
        while (current != null) {
 
1500
            if (current.actualBaseURI != null 
 
1501
              && current.actualBaseURI.length() != 0) {
 
1502
                return current.actualBaseURI;
 
1503
            }
 
1504
            current = current.getParent();
 
1505
        }
 
1506
        return "";
 
1507
        
 
1508
    }
 
1509
        
 
1510
    
 
1511
    private boolean legalURI(String base) {
 
1512
        
 
1513
        try {
 
1514
            Verifier.checkURIReference(base);
 
1515
            return true;
 
1516
        }
 
1517
        catch (MalformedURIException ex) {
 
1518
            return false;
 
1519
        }
 
1520
        
 
1521
    }
 
1522
 
 
1523
 
 
1524
    /**
 
1525
     * <p>
 
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.
 
1530
     * </p>
 
1531
     * 
 
1532
     * @return the XML representation of this element
 
1533
     * 
 
1534
     */
 
1535
    public final String toXML() {
 
1536
        
 
1537
        StringBuffer result = new StringBuffer(1024);
 
1538
        Node current = this;
 
1539
        boolean endTag = false;
 
1540
        int index = -1;
 
1541
        int[] indexes = new int[10];
 
1542
        int top = 0;
 
1543
        indexes[0] = -1;
 
1544
        
 
1545
        while (true) {
 
1546
            
 
1547
            if (!endTag && current.getChildCount() > 0) {
 
1548
               writeStartTag((Element) current, result);
 
1549
               current = current.getChild(0);
 
1550
               index = 0;
 
1551
               top++;
 
1552
               indexes = grow(indexes, top);
 
1553
               indexes[top] = 0;
 
1554
            }
 
1555
            else {
 
1556
              if (endTag) {
 
1557
                 writeEndTag((Element) current, result);
 
1558
                 if (current == this) break;
 
1559
              }
 
1560
              else if (current.isElement()) {
 
1561
                 writeStartTag((Element) current, result);
 
1562
                 if (current == this) break;
 
1563
              }
 
1564
              else {
 
1565
                  result.append(current.toXML());
 
1566
              }
 
1567
              endTag = false;
 
1568
              ParentNode parent = current.getParent();
 
1569
              if (parent.getChildCount() - 1 == index) {
 
1570
                current = parent;
 
1571
                top--;
 
1572
                if (current != this) {
 
1573
                    index = indexes[top];
 
1574
                }
 
1575
                endTag = true;
 
1576
              }
 
1577
              else {
 
1578
                 index++;
 
1579
                 indexes[top] = index;
 
1580
                 current = parent.getChild(index);
 
1581
              }
 
1582
              
 
1583
            }
 
1584
 
 
1585
        }        
 
1586
 
 
1587
        return result.toString();
 
1588
        
 
1589
    }
 
1590
    
 
1591
    
 
1592
    private static void writeStartTag(Element element, StringBuffer result) {
 
1593
        
 
1594
        result.append("<");
 
1595
        result.append(element.getQualifiedName());
 
1596
 
 
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;   
 
1603
               if (uri.equals(
 
1604
                 parentElement.getNamespaceURI(additionalPrefix))) {
 
1605
                   continue;
 
1606
               } 
 
1607
            }
 
1608
            else if (uri.length() == 0) {
 
1609
                continue; // no need to say xmlns=""   
 
1610
            }
 
1611
            
 
1612
            result.append(" xmlns"); 
 
1613
            if (additionalPrefix.length() > 0) {
 
1614
                result.append(':'); 
 
1615
                result.append(additionalPrefix); 
 
1616
            }
 
1617
            result.append("=\""); 
 
1618
            result.append(uri);   
 
1619
            result.append('"');
 
1620
        }
 
1621
        
 
1622
        // attributes
 
1623
        if (element.attributes != null) {
 
1624
            for (int i = 0; i < element.numAttributes; i++) {
 
1625
                Attribute attribute = element.attributes[i];
 
1626
                result.append(' ');
 
1627
                result.append(attribute.toXML());   
 
1628
            }       
 
1629
        }
 
1630
 
 
1631
        if (element.getChildCount() > 0) {
 
1632
            result.append('>');
 
1633
        }
 
1634
        else {
 
1635
            result.append(" />");               
 
1636
        }
 
1637
        
 
1638
    }
 
1639
 
 
1640
    
 
1641
    private static void writeEndTag(
 
1642
      Element element, StringBuffer result) {
 
1643
        
 
1644
        result.append("</");
 
1645
        result.append(element.getQualifiedName());
 
1646
        result.append(">");
 
1647
        
 
1648
    }
 
1649
    
 
1650
 
 
1651
    /**
 
1652
     * <p>
 
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.
 
1657
     * </p>
 
1658
     * 
 
1659
     * @return XPath string value of this element
 
1660
     * 
 
1661
     */
 
1662
    public final String getValue() {
 
1663
 
 
1664
        // non-recursive algorithm avoids stack size limitations
 
1665
        int childCount = this.getChildCount();
 
1666
        if (childCount == 0) return "";
 
1667
 
 
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();
 
1673
        }   
 
1674
        
 
1675
        StringBuffer result = new StringBuffer();
 
1676
        int index = 0;
 
1677
        int[] indexes = new int[10];
 
1678
        int top = 0;
 
1679
        indexes[0] = 0;
 
1680
        
 
1681
        boolean endTag = false;
 
1682
        while (true) {
 
1683
            if (!endTag && current.getChildCount() > 0) {
 
1684
               current = current.getChild(0);
 
1685
               index = 0;
 
1686
               top++;
 
1687
               indexes = grow(indexes, top);
 
1688
               indexes[top] = 0;            }
 
1689
            else {
 
1690
                endTag = false;
 
1691
                if (current.isText()) result.append(current.getValue());
 
1692
                ParentNode parent = current.getParent();
 
1693
                if (parent.getChildCount() - 1 == index) {
 
1694
                    current = parent;
 
1695
                    top--;
 
1696
                    if (current == this) break;
 
1697
                    index = indexes[top];
 
1698
                    endTag = true;
 
1699
                }
 
1700
                else {
 
1701
                    index++;
 
1702
                    indexes[top] = index;
 
1703
                    current = parent.getChild(index);
 
1704
                }
 
1705
            }
 
1706
        }        
 
1707
        
 
1708
        return result.toString();
 
1709
 
 
1710
    }
 
1711
 
 
1712
    /**
 
1713
     * <p>
 
1714
     * Creates a deep copy of this element with no parent,
 
1715
     * that can be added to this document or a different one.
 
1716
     * </p>
 
1717
     * 
 
1718
     * <p>
 
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} 
 
1724
     * method instead.
 
1725
     * </p>
 
1726
     * 
 
1727
     * @return a deep copy of this element with no parent
 
1728
     */
 
1729
    public Node copy() {
 
1730
        Element result = copyTag(this);
 
1731
        copyChildren(this, result);
 
1732
        return result;
 
1733
    }
 
1734
    
 
1735
    
 
1736
    /**
 
1737
     * <p>
 
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}. 
 
1744
     * </p>
 
1745
     * 
 
1746
     * <p>
 
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.
 
1752
     * </p>
 
1753
     *
 
1754
     * @return an empty element with the same name and 
 
1755
     *     namespace as this element
 
1756
     */
 
1757
    protected Element shallowCopy() {      
 
1758
        return new Element(getQualifiedName(), getNamespaceURI());
 
1759
    }
 
1760
 
 
1761
    
 
1762
    /**
 
1763
     * <p>
 
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.
 
1767
     * </p>
 
1768
     * 
 
1769
     * @return a non-XML string representation of this element
 
1770
     */
 
1771
    public final String toString() {
 
1772
        return 
 
1773
          "[" + getClass().getName() + ": " + getQualifiedName() + "]";
 
1774
    }
 
1775
 
 
1776
    
 
1777
    boolean isElement() {
 
1778
        return true;   
 
1779
    } 
 
1780
    
 
1781
    
 
1782
    private void checkPrefixConflict(Attribute attribute) {
 
1783
        
 
1784
        String prefix = attribute.getNamespacePrefix();
 
1785
        String namespaceURI = attribute.getNamespaceURI();
 
1786
        
 
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());
 
1795
            }   
 
1796
        }
 
1797
        
 
1798
    }
 
1799
 
 
1800
 
 
1801
    Iterator attributeIterator() {
 
1802
 
 
1803
        return new Iterator() {
 
1804
 
 
1805
            private int next = 0;
 
1806
            
 
1807
            public boolean hasNext() {
 
1808
                return next < numAttributes;
 
1809
            }
 
1810
 
 
1811
            public Object next() throws NoSuchElementException {
 
1812
                
 
1813
                if (hasNext()) {
 
1814
                    Attribute a = attributes[next];
 
1815
                    next++;
 
1816
                    return a;
 
1817
                }
 
1818
                throw new NoSuchElementException("No such attribute");
 
1819
                
 
1820
            }
 
1821
 
 
1822
            public void remove() {
 
1823
                throw new UnsupportedOperationException();
 
1824
            }
 
1825
            
 
1826
        };
 
1827
    }
 
1828
 
 
1829
    
 
1830
}
 
 
b'\\ No newline at end of file'