29b8dc72e536b91fec1aba7f73459746e11c3af8
[gcc.git] / libjava / classpath / gnu / xml / dom / DomDocument.java
1 /* DomDocument.java --
2 Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
37
38 package gnu.xml.dom;
39
40 import java.util.Iterator;
41 import javax.xml.XMLConstants;
42
43 import org.w3c.dom.Attr;
44 import org.w3c.dom.CDATASection;
45 import org.w3c.dom.Comment;
46 import org.w3c.dom.Document;
47 import org.w3c.dom.DocumentFragment;
48 import org.w3c.dom.DocumentType;
49 import org.w3c.dom.DOMConfiguration;
50 import org.w3c.dom.DOMImplementation;
51 import org.w3c.dom.DOMException;
52 import org.w3c.dom.Element;
53 import org.w3c.dom.Entity;
54 import org.w3c.dom.EntityReference;
55 import org.w3c.dom.NamedNodeMap;
56 import org.w3c.dom.Node;
57 import org.w3c.dom.Notation;
58 import org.w3c.dom.ProcessingInstruction;
59 import org.w3c.dom.Text;
60 import org.w3c.dom.UserDataHandler;
61 import org.w3c.dom.traversal.DocumentTraversal;
62 import org.w3c.dom.traversal.NodeFilter;
63 import org.w3c.dom.traversal.NodeIterator;
64 import org.w3c.dom.traversal.TreeWalker;
65 import org.w3c.dom.xpath.XPathEvaluator;
66 import org.w3c.dom.xpath.XPathException;
67 import org.w3c.dom.xpath.XPathExpression;
68 import org.w3c.dom.xpath.XPathNSResolver;
69
70 /**
71 * <p> "Document" and "DocumentTraversal" implementation.
72 *
73 * <p> Note that when this checks names for legality, it uses an
74 * approximation of the XML rules, not the real ones. Specifically,
75 * it uses Unicode rules, with sufficient tweaks to pass a majority
76 * of basic XML conformance tests. (The huge XML character tables are
77 * hairy to implement.)
78 *
79 * @author David Brownell
80 * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
81 */
82 public class DomDocument
83 extends DomNode
84 implements Document, DocumentTraversal, XPathEvaluator
85 {
86
87 private final DOMImplementation implementation;
88 private boolean checkingCharacters = true;
89 boolean checkingWellformedness = true;
90
91 boolean building; // if true, skip mutation events in the tree
92
93 DomDocumentConfiguration config;
94
95 String inputEncoding;
96 String encoding;
97 String version = "1.0";
98 boolean standalone;
99 String systemId;
100
101 /**
102 * Constructs a Document node, associating it with an instance
103 * of the DomImpl class.
104 *
105 * <p> Note that this constructor disables character checking.
106 * It is normally used when connecting a DOM to an XML parser,
107 * and duplicating such checks is undesirable. When used for
108 * purposes other than connecting to a parser, you should
109 * re-enable that checking.
110 *
111 * @see #setCheckingCharacters
112 */
113 public DomDocument()
114 {
115 this(new DomImpl());
116 }
117
118 /**
119 * Constructs a Document node, associating it with the specified
120 * implementation. This should only be used in conjunction with
121 * a specialized implementation; it will normally be called by
122 * that implementation.
123 *
124 * @see DomImpl
125 * @see #setCheckingCharacters
126 */
127 protected DomDocument(DOMImplementation impl)
128 {
129 super(DOCUMENT_NODE, null);
130 implementation = impl;
131 }
132
133 /**
134 * Sets the <code>building</code> flag.
135 * Mutation events in the document are not reported.
136 */
137 public void setBuilding(boolean flag)
138 {
139 building = flag;
140 }
141
142 /**
143 * Sets whether to check for document well-formedness.
144 * If true, an exception will be raised if a second doctype or root
145 * element node is added to the document.
146 */
147 public void setCheckWellformedness(boolean flag)
148 {
149 checkingWellformedness = flag;
150 }
151
152 /**
153 * <b>DOM L1</b>
154 * Returns the constant "#document".
155 */
156 final public String getNodeName()
157 {
158 return "#document";
159 }
160
161 /**
162 * <b>DOM L1</b>
163 * Returns the document's root element, or null.
164 */
165 final public Element getDocumentElement()
166 {
167 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
168 {
169 if (ctx.nodeType == ELEMENT_NODE)
170 {
171 return (Element) ctx;
172 }
173 }
174 return null;
175 }
176
177 /**
178 * <b>DOM L1</b>
179 * Returns the document's DocumentType, or null.
180 */
181 final public DocumentType getDoctype()
182 {
183 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
184 {
185 if (ctx.nodeType == DOCUMENT_TYPE_NODE)
186 {
187 return (DocumentType) ctx;
188 }
189 }
190 return null;
191 }
192
193 /**
194 * <b>DOM L1</b>
195 * Returns the document's DOMImplementation.
196 */
197 final public DOMImplementation getImplementation()
198 {
199 return implementation;
200 }
201
202 /**
203 * <b>DOM L1 (relocated in DOM L2)</b>
204 * Returns the element with the specified "ID" attribute, or null.
205 *
206 * <p>Returns null unless {@link Consumer} was used to populate internal
207 * DTD declaration information, using package-private APIs. If that
208 * internal DTD information is available, the document may be searched for
209 * the element with that ID.
210 */
211 public Element getElementById(String id)
212 {
213 if (id == null || id.length() == 0)
214 {
215 return null;
216 }
217 DomDoctype doctype = (DomDoctype) getDoctype();
218 if (doctype != null && !doctype.hasIds())
219 {
220 doctype = null;
221 }
222
223 // yes, this is linear in size of document.
224 // it'd be easy enough to maintain a hashtable.
225 Node current = getDocumentElement();
226 Node temp;
227
228 if (current == null)
229 {
230 return null;
231 }
232 while (current != this)
233 {
234 // done?
235 if (current.getNodeType() == ELEMENT_NODE)
236 {
237 DomElement element = (DomElement) current;
238 if (doctype != null)
239 {
240 DTDElementTypeInfo info =
241 doctype.getElementTypeInfo(current.getNodeName());
242 if (info != null &&
243 id.equals(element.getAttribute(info.idAttrName)))
244 {
245 return element;
246 }
247 else if (element.userIdAttrs != null)
248 {
249 for (Iterator i = element.userIdAttrs.iterator();
250 i.hasNext(); )
251 {
252 Node idAttr = (Node) i.next();
253 if (id.equals(idAttr.getNodeValue()))
254 {
255 return element;
256 }
257 }
258 }
259 }
260 // xml:id
261 String xmlId = element.getAttribute("xml:id");
262 if (xmlId == null)
263 {
264 xmlId = element.getAttributeNS(XMLConstants.XML_NS_URI,
265 "id");
266 }
267 if (id.equals(xmlId))
268 {
269 return element;
270 }
271 }
272
273 // descend?
274 if (current.hasChildNodes())
275 {
276 current = current.getFirstChild();
277 continue;
278 }
279
280 // lateral?
281 temp = current.getNextSibling();
282 if (temp != null)
283 {
284 current = temp;
285 continue;
286 }
287
288 // back up ...
289 do
290 {
291 temp = current.getParentNode();
292 if (temp == null)
293 {
294 return null;
295 }
296 current = temp;
297 temp = current.getNextSibling();
298 }
299 while (temp == null);
300 current = temp;
301 }
302 return null;
303 }
304
305 private void checkNewChild(Node newChild)
306 {
307 if (newChild.getNodeType() == ELEMENT_NODE
308 && getDocumentElement() != null)
309 {
310 throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
311 "document element already present: " +
312 getDocumentElement(), newChild, 0);
313 }
314 if (newChild.getNodeType() == DOCUMENT_TYPE_NODE
315 && getDoctype() != null)
316 {
317 throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
318 "document type already present: " +
319 getDoctype(), newChild, 0);
320 }
321 }
322
323 /**
324 * <b>DOM L1</b>
325 * Appends the specified node to this node's list of children,
326 * enforcing the constraints that there be only one root element
327 * and one document type child.
328 */
329 public Node appendChild(Node newChild)
330 {
331 if (checkingWellformedness)
332 {
333 checkNewChild(newChild);
334 }
335 return super.appendChild(newChild);
336 }
337
338 /**
339 * <b>DOM L1</b>
340 * Inserts the specified node in this node's list of children,
341 * enforcing the constraints that there be only one root element
342 * and one document type child.
343 */
344 public Node insertBefore(Node newChild, Node refChild)
345 {
346 if (checkingWellformedness)
347 {
348 checkNewChild(newChild);
349 }
350 return super.insertBefore(newChild, refChild);
351 }
352
353 /**
354 * <b>DOM L1</b>
355 * Replaces the specified node in this node's list of children,
356 * enforcing the constraints that there be only one root element
357 * and one document type child.
358 */
359 public Node replaceChild(Node newChild, Node refChild)
360 {
361 if (checkingWellformedness &&
362 ((newChild.getNodeType() == ELEMENT_NODE &&
363 refChild.getNodeType() != ELEMENT_NODE) ||
364 (newChild.getNodeType() == DOCUMENT_TYPE_NODE &&
365 refChild.getNodeType() != DOCUMENT_TYPE_NODE)))
366 {
367 checkNewChild(newChild);
368 }
369 return super.replaceChild(newChild, refChild);
370 }
371
372 // NOTE: DOM can't really tell when the name of an entity,
373 // notation, or PI must follow the namespace rules (excluding
374 // colons) instead of the XML rules (which allow them without
375 // much restriction). That's an API issue. verifyXmlName
376 // aims to enforce the XML rules, not the namespace rules.
377
378 /**
379 * Throws a DOM exception if the specified name is not a legal XML 1.0
380 * Name.
381 * @deprecated This method is deprecated and may be removed in future
382 * versions of GNU JAXP
383 */
384 public static void verifyXmlName(String name)
385 {
386 // XXX why is this public?
387 checkName(name, false);
388 }
389
390 static void checkName(String name, boolean xml11)
391 {
392 if (name == null)
393 {
394 throw new DomDOMException(DOMException.NAMESPACE_ERR, name, null, 0);
395 }
396 int len = name.length();
397 if (len == 0)
398 {
399 throw new DomDOMException(DOMException.NAMESPACE_ERR, name, null, 0);
400 }
401
402 // dog: rewritten to use the rules for XML 1.0 and 1.1
403
404 // Name start character
405 char c = name.charAt(0);
406 if (xml11)
407 {
408 // XML 1.1
409 if ((c < 0x0041 || c > 0x005a) &&
410 (c < 0x0061 || c > 0x007a) &&
411 c != ':' && c != '_' &&
412 (c < 0x00c0 || c > 0x00d6) &&
413 (c < 0x00d8 || c > 0x00f6) &&
414 (c < 0x00f8 || c > 0x02ff) &&
415 (c < 0x0370 || c > 0x037d) &&
416 (c < 0x037f || c > 0x1fff) &&
417 (c < 0x200c || c > 0x200d) &&
418 (c < 0x2070 || c > 0x218f) &&
419 (c < 0x2c00 || c > 0x2fef) &&
420 (c < 0x3001 || c > 0xd7ff) &&
421 (c < 0xf900 || c > 0xfdcf) &&
422 (c < 0xfdf0 || c > 0xfffd) &&
423 (c < 0x10000 || c > 0xeffff))
424 {
425 throw new DomDOMException(DOMException.INVALID_CHARACTER_ERR,
426 name, null, c);
427 }
428 }
429 else
430 {
431 // XML 1.0
432 int type = Character.getType(c);
433 switch (type)
434 {
435 case Character.LOWERCASE_LETTER: // Ll
436 case Character.UPPERCASE_LETTER: // Lu
437 case Character.OTHER_LETTER: // Lo
438 case Character.TITLECASE_LETTER: // Lt
439 case Character.LETTER_NUMBER: // Nl
440 if ((c > 0xf900 && c < 0xfffe) ||
441 (c >= 0x20dd && c <= 0x20e0))
442 {
443 // Compatibility area and Unicode 2.0 exclusions
444 throw new DomDOMException(DOMException.INVALID_CHARACTER_ERR,
445 name, null, c);
446 }
447 break;
448 default:
449 if (c != ':' && c != '_' && (c < 0x02bb || c > 0x02c1) &&
450 c != 0x0559 && c != 0x06e5 && c != 0x06e6)
451 {
452 throw new DomDOMException(DOMException.INVALID_CHARACTER_ERR,
453 name, null, c);
454 }
455 }
456 }
457
458 // Subsequent characters
459 for (int i = 1; i < len; i++)
460 {
461 c = name.charAt(i);
462 if (xml11)
463 {
464 // XML 1.1
465 if ((c < 0x0041 || c > 0x005a) &&
466 (c < 0x0061 || c > 0x007a) &&
467 (c < 0x0030 || c > 0x0039) &&
468 c != ':' && c != '_' && c != '-' && c != '.' &&
469 (c < 0x00c0 || c > 0x00d6) &&
470 (c < 0x00d8 || c > 0x00f6) &&
471 (c < 0x00f8 || c > 0x02ff) &&
472 (c < 0x0370 || c > 0x037d) &&
473 (c < 0x037f || c > 0x1fff) &&
474 (c < 0x200c || c > 0x200d) &&
475 (c < 0x2070 || c > 0x218f) &&
476 (c < 0x2c00 || c > 0x2fef) &&
477 (c < 0x3001 || c > 0xd7ff) &&
478 (c < 0xf900 || c > 0xfdcf) &&
479 (c < 0xfdf0 || c > 0xfffd) &&
480 (c < 0x10000 || c > 0xeffff) &&
481 c != 0x00b7 &&
482 (c < 0x0300 || c > 0x036f) &&
483 (c < 0x203f || c > 0x2040))
484 {
485 throw new DomDOMException(DOMException.INVALID_CHARACTER_ERR, name,
486 null, c);
487 }
488 }
489 else
490 {
491 // XML 1.0
492 int type = Character.getType(c);
493 switch (type)
494 {
495 case Character.LOWERCASE_LETTER: // Ll
496 case Character.UPPERCASE_LETTER: // Lu
497 case Character.DECIMAL_DIGIT_NUMBER: // Nd
498 case Character.OTHER_LETTER: // Lo
499 case Character.TITLECASE_LETTER: // Lt
500 case Character.LETTER_NUMBER: // Nl
501 case Character.COMBINING_SPACING_MARK: // Mc
502 case Character.ENCLOSING_MARK: // Me
503 case Character.NON_SPACING_MARK: // Mn
504 case Character.MODIFIER_LETTER: // Lm
505 if ((c > 0xf900 && c < 0xfffe) ||
506 (c >= 0x20dd && c <= 0x20e0))
507 {
508 // Compatibility area and Unicode 2.0 exclusions
509 throw new DomDOMException(DOMException.INVALID_CHARACTER_ERR,
510 name, null, c);
511 }
512 break;
513 default:
514 if (c != '-' && c != '.' && c != ':' && c != '_' &&
515 c != 0x0387 && (c < 0x02bb || c > 0x02c1) &&
516 c != 0x0559 && c != 0x06e5 && c != 0x06e6 && c != 0x00b7)
517 {
518 throw new DomDOMException(DOMException.INVALID_CHARACTER_ERR,
519 name, null, c);
520 }
521 }
522 }
523 }
524
525 // FIXME characters with a font or compatibility decomposition (i.e.
526 // those with a "compatibility formatting tag" in field 5 of the
527 // database -- marked by field 5 beginning with a "<") are not allowed.
528 }
529
530 // package private
531 static void checkNCName(String name, boolean xml11)
532 {
533 checkName(name, xml11);
534 int len = name.length();
535 int index = name.indexOf(':');
536 if (index != -1)
537 {
538 if (index == 0 || index == (len - 1) ||
539 name.lastIndexOf(':') != index)
540 {
541 throw new DomDOMException(DOMException.NAMESPACE_ERR,
542 name, null, 0);
543 }
544 }
545 }
546
547 // package private
548 static void checkChar(String value, boolean xml11)
549 {
550 char[] chars = value.toCharArray();
551 checkChar(chars, 0, chars.length, xml11);
552 }
553
554 static void checkChar(char[] buf, int off, int len, boolean xml11)
555 {
556 for (int i = 0; i < len; i++)
557 {
558 char c = buf[i];
559
560 // assume surrogate pairing checks out OK, for simplicity
561 if ((c >= 0x0020 && c <= 0xd7ff) ||
562 (c == 0x000a || c == 0x000d || c == 0x0009) ||
563 (c >= 0xe000 && c <= 0xfffd) ||
564 (c >= 0x10000 && c <= 0x10ffff))
565 {
566 continue;
567 }
568 if (xml11)
569 {
570 if ((c >= 0x0001 && c <= 0x001f) ||
571 (c >= 0x007f && c <= 0x0084) ||
572 (c >= 0x0086 && c <= 0x009f))
573 {
574 continue;
575 }
576 }
577 throw new DomDOMException(DOMException.INVALID_CHARACTER_ERR,
578 new String(buf, off, len), null, c);
579 }
580 }
581
582 /**
583 * <b>DOM L1</b>
584 * Returns a newly created element with the specified name.
585 */
586 public Element createElement(String name)
587 {
588 Element element;
589
590 if (checkingCharacters)
591 {
592 checkName(name, "1.1".equals(version));
593 }
594 if (name.startsWith("xml:"))
595 {
596 element = createElementNS(null, name);
597 }
598 else
599 {
600 DomElement domElement = new DomElement(this, null, name);
601 domElement.localName = null;
602 element = domElement;
603 }
604 defaultAttributes(element, name);
605 return element;
606 }
607
608 /**
609 * <b>DOM L2</b>
610 * Returns a newly created element with the specified name
611 * and namespace information.
612 */
613 public Element createElementNS(String namespaceURI, String name)
614 {
615 if (checkingCharacters)
616 {
617 checkNCName(name, "1.1".equals(version));
618 }
619
620 if ("".equals(namespaceURI))
621 {
622 namespaceURI = null;
623 }
624 if (name.startsWith("xml:"))
625 {
626 if (namespaceURI != null
627 && !XMLConstants.XML_NS_URI.equals(namespaceURI))
628 {
629 throw new DomDOMException(DOMException.NAMESPACE_ERR,
630 "xml namespace is always " +
631 XMLConstants.XML_NS_URI, this, 0);
632 }
633 namespaceURI = XMLConstants.XML_NS_URI;
634 }
635 else if (XMLConstants.XMLNS_ATTRIBUTE.equals(name) ||
636 name.startsWith("xmlns:"))
637 {
638 throw new DomDOMException(DOMException.NAMESPACE_ERR,
639 "xmlns is reserved", this, 0);
640 }
641 else if (namespaceURI == null && name.indexOf(':') != -1)
642 {
643 throw new DomDOMException(DOMException.NAMESPACE_ERR,
644 "prefixed name '" + name +
645 "' needs a URI", this, 0);
646 }
647
648 Element element = new DomElement(this, namespaceURI, name);
649 defaultAttributes(element, name);
650 return element;
651 }
652
653 private void defaultAttributes(Element element, String name)
654 {
655 DomDoctype doctype = (DomDoctype) getDoctype();
656 if (doctype == null)
657 {
658 return;
659 }
660
661 // default any attributes that need it
662 DTDElementTypeInfo info = doctype.getElementTypeInfo(name);
663 if (info != null)
664 {
665 for (Iterator i = info.attributes(); i != null && i.hasNext(); )
666 {
667 DTDAttributeTypeInfo attr = (DTDAttributeTypeInfo) i.next();
668 DomAttr node = (DomAttr) createAttribute(attr.name);
669
670 String value = attr.value;
671 if (value == null)
672 {
673 value = "";
674 }
675 node.setValue(value);
676 node.setSpecified(false);
677 element.setAttributeNode(node);
678 }
679 }
680 }
681
682 /**
683 * <b>DOM L1</b>
684 * Returns a newly created document fragment.
685 */
686 public DocumentFragment createDocumentFragment()
687 {
688 return new DomDocumentFragment(this);
689 }
690
691 /**
692 * <b>DOM L1</b>
693 * Returns a newly created text node with the specified value.
694 */
695 public Text createTextNode(String value)
696 {
697 if (checkingCharacters)
698 {
699 checkChar(value, "1.1".equals(version));
700 }
701 return new DomText(this, value);
702 }
703
704 /**
705 * Returns a newly created text node with the specified value.
706 */
707 public Text createTextNode(char[] buf, int off, int len)
708 {
709 if (checkingCharacters)
710 {
711 checkChar(buf, off, len, "1.1".equals(version));
712 }
713 return new DomText(this, buf, off, len);
714 }
715
716 /**
717 * <b>DOM L1</b>
718 * Returns a newly created comment node with the specified value.
719 */
720 public Comment createComment(String value)
721 {
722 if (checkingCharacters)
723 {
724 checkChar(value, "1.1".equals(version));
725 }
726 return new DomComment(this, value);
727 }
728
729 /**
730 * <b>DOM L1</b>
731 * Returns a newly created CDATA section node with the specified value.
732 */
733 public CDATASection createCDATASection(String value)
734 {
735 if (checkingCharacters)
736 {
737 checkChar(value, "1.1".equals(version));
738 }
739 return new DomCDATASection(this, value);
740 }
741
742 /**
743 * Returns a newly created CDATA section node with the specified value.
744 */
745 public CDATASection createCDATASection(char[] buf, int off, int len)
746 {
747 if (checkingCharacters)
748 {
749 checkChar(buf, off, len, "1.1".equals(version));
750 }
751 return new DomCDATASection(this, buf, off, len);
752 }
753
754 /**
755 * <b>DOM L1</b>
756 * Returns a newly created processing instruction.
757 */
758 public ProcessingInstruction createProcessingInstruction(String target,
759 String data)
760 {
761 if (checkingCharacters)
762 {
763 boolean xml11 = "1.1".equals(version);
764 checkName(target, xml11);
765 if ("xml".equalsIgnoreCase(target))
766 {
767 throw new DomDOMException(DOMException.SYNTAX_ERR,
768 "illegal PI target name",
769 this, 0);
770 }
771 checkChar(data, xml11);
772 }
773 return new DomProcessingInstruction(this, target, data);
774 }
775
776 /**
777 * <b>DOM L1</b>
778 * Returns a newly created attribute with the specified name.
779 */
780 public Attr createAttribute(String name)
781 {
782 if (checkingCharacters)
783 {
784 checkName(name, "1.1".equals(version));
785 }
786 if (name.startsWith("xml:"))
787 {
788 return createAttributeNS(XMLConstants.XML_NS_URI, name);
789 }
790 else if (XMLConstants.XMLNS_ATTRIBUTE.equals(name) ||
791 name.startsWith("xmlns:"))
792 {
793 return createAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, name);
794 }
795 else
796 {
797 DomAttr ret = new DomAttr(this, null, name);
798 ret.localName = null;
799 return ret;
800 }
801 }
802
803 /**
804 * <b>DOM L2</b>
805 * Returns a newly created attribute with the specified name
806 * and namespace information.
807 */
808 public Attr createAttributeNS(String namespaceURI, String name)
809 {
810 if (checkingCharacters)
811 {
812 checkNCName(name, "1.1".equals(version));
813 }
814
815 if ("".equals(namespaceURI))
816 {
817 namespaceURI = null;
818 }
819 if (name.startsWith ("xml:"))
820 {
821 if (namespaceURI == null)
822 {
823 namespaceURI = XMLConstants.XML_NS_URI;
824 }
825 else if (!XMLConstants.XML_NS_URI.equals(namespaceURI))
826 {
827 throw new DomDOMException(DOMException.NAMESPACE_ERR,
828 "xml namespace is always " +
829 XMLConstants.XML_NS_URI,
830 this, 0);
831 }
832 }
833 else if (XMLConstants.XMLNS_ATTRIBUTE.equals(name) ||
834 name.startsWith("xmlns:"))
835 {
836 if (namespaceURI == null)
837 {
838 namespaceURI = XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
839 }
840 else if (!XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI))
841 {
842 throw new DomDOMException(DOMException.NAMESPACE_ERR,
843 "xmlns namespace must be " +
844 XMLConstants.XMLNS_ATTRIBUTE_NS_URI,
845 this, 0);
846 }
847 }
848 else if (namespaceURI == null && name.indexOf(':') != -1)
849 {
850 throw new DomDOMException(DOMException.NAMESPACE_ERR,
851 "prefixed name needs a URI: " + name, this, 0);
852 }
853 return new DomAttr(this, namespaceURI, name);
854 }
855
856 /**
857 * <b>DOM L1</b>
858 * Returns a newly created reference to the specified entity.
859 * The caller should populate this with the appropriate children
860 * and then mark it as readonly.
861 *
862 * @see DomNode#makeReadonly
863 */
864 public EntityReference createEntityReference(String name)
865 {
866 DomEntityReference ret = new DomEntityReference(this, name);
867 DocumentType doctype = getDoctype();
868 if (doctype != null)
869 {
870 DomEntity ent = (DomEntity) doctype.getEntities().getNamedItem(name);
871 if (ent != null)
872 {
873 for (DomNode ctx = ent.first; ctx != null; ctx = ctx.next)
874 {
875 ret.appendChild(ctx.cloneNode(true));
876 }
877 }
878 }
879 ret.makeReadonly();
880 return ret;
881 }
882
883 /**
884 * <b>DOM L2</b>
885 * Makes a copy of the specified node, with all nodes "owned" by
886 * this document and with children optionally copied. This type
887 * of standard utility has become, well, a standard utility.
888 *
889 * <p> Note that EntityReference nodes created through this method (either
890 * directly, or recursively) never have children, and that there is no
891 * portable way to associate them with such children.
892 *
893 * <p> Note also that there is no requirement that the specified node
894 * be associated with a different document. This differs from the
895 * <em>cloneNode</em> operation in that the node itself is not given
896 * an opportunity to participate, so that any information managed
897 * by node subclasses will be lost.
898 */
899 public Node importNode(Node src, boolean deep)
900 {
901 Node dst = null;
902 switch (src.getNodeType())
903 {
904 case TEXT_NODE:
905 dst = createTextNode(src.getNodeValue());
906 break;
907 case CDATA_SECTION_NODE:
908 dst = createCDATASection(src.getNodeValue());
909 break;
910 case COMMENT_NODE:
911 dst = createComment(src.getNodeValue());
912 break;
913 case PROCESSING_INSTRUCTION_NODE:
914 dst = createProcessingInstruction(src.getNodeName(),
915 src.getNodeValue());
916 break;
917 case NOTATION_NODE:
918 // NOTE: There's no standard way to create
919 // these, or add them to a doctype. Useless.
920 Notation notation = (Notation) src;
921 dst = new DomNotation(this, notation.getNodeName(),
922 notation.getPublicId(),
923 notation.getSystemId());
924 break;
925 case ENTITY_NODE:
926 // NOTE: There's no standard way to create
927 // these, or add them to a doctype. Useless.
928 Entity entity = (Entity) src;
929 dst = new DomEntity(this, entity.getNodeName(),
930 entity.getPublicId(),
931 entity.getSystemId(),
932 entity.getNotationName());
933 if (deep)
934 {
935 for (Node ctx = src.getFirstChild(); ctx != null;
936 ctx = ctx.getNextSibling())
937 {
938 dst.appendChild(importNode(ctx, deep));
939 }
940 }
941 break;
942 case ENTITY_REFERENCE_NODE:
943 dst = createEntityReference(src.getNodeName());
944 break;
945 case DOCUMENT_FRAGMENT_NODE:
946 dst = new DomDocumentFragment(this);
947 if (deep)
948 {
949 for (Node ctx = src.getFirstChild(); ctx != null;
950 ctx = ctx.getNextSibling())
951 {
952 dst.appendChild(importNode(ctx, deep));
953 }
954 }
955 break;
956 case ATTRIBUTE_NODE:
957 String attr_nsuri = src.getNamespaceURI();
958 if (attr_nsuri != null)
959 {
960 dst = createAttributeNS(attr_nsuri, src.getNodeName());
961 }
962 else
963 {
964 dst = createAttribute(src.getNodeName());
965 }
966 // this is _always_ done regardless of "deep" setting
967 for (Node ctx = src.getFirstChild(); ctx != null;
968 ctx = ctx.getNextSibling())
969 {
970 dst.appendChild(importNode(ctx, false));
971 }
972 break;
973 case ELEMENT_NODE:
974 String elem_nsuri = src.getNamespaceURI();
975 if (elem_nsuri != null)
976 {
977 dst = createElementNS(elem_nsuri, src.getNodeName());
978 }
979 else
980 {
981 dst = createElement(src.getNodeName());
982 }
983 NamedNodeMap srcAttrs = src.getAttributes();
984 NamedNodeMap dstAttrs = dst.getAttributes();
985 int len = srcAttrs.getLength();
986 for (int i = 0; i < len; i++)
987 {
988 Attr a = (Attr) srcAttrs.item(i);
989 Attr dflt;
990
991 // maybe update defaulted attributes
992 dflt = (Attr) dstAttrs.getNamedItem(a.getNodeName());
993 if (dflt != null)
994 {
995 String newval = a.getNodeValue();
996 if (!dflt.getNodeValue().equals(newval)
997 || a.getSpecified () == true)
998 {
999 dflt.setNodeValue (newval);
1000 }
1001 continue;
1002 }
1003
1004 dstAttrs.setNamedItem((Attr) importNode(a, false));
1005 }
1006 if (deep)
1007 {
1008 for (Node ctx = src.getFirstChild(); ctx != null;
1009 ctx = ctx.getNextSibling())
1010 {
1011 dst.appendChild(importNode(ctx, true));
1012 }
1013 }
1014 break;
1015 // can't import document or doctype nodes
1016 case DOCUMENT_NODE:
1017 case DOCUMENT_TYPE_NODE:
1018 // FALLTHROUGH
1019 // can't import unrecognized or nonstandard nodes
1020 default:
1021 throw new DomDOMException(DOMException.NOT_SUPPORTED_ERR, null, src, 0);
1022 }
1023
1024 // FIXME cleanup a bit -- for deep copies, copy those
1025 // children in one place, here (code sharing is healthy)
1026
1027 if (src instanceof DomNode)
1028 {
1029 ((DomNode) src).notifyUserDataHandlers(UserDataHandler.NODE_IMPORTED,
1030 src, dst);
1031 }
1032 return dst;
1033 }
1034
1035 /**
1036 * <b>DOM L2 (Traversal)</b>
1037 * Returns a newly created node iterator. Don't forget to detach
1038 * this iterator when you're done using it!
1039 *
1040 * @see DomIterator
1041 */
1042 public NodeIterator createNodeIterator(Node root,
1043 int whatToShow,
1044 NodeFilter filter,
1045 boolean expandEntities)
1046 {
1047 return new DomNodeIterator(root, whatToShow, filter, expandEntities,
1048 false);
1049 }
1050
1051 public TreeWalker createTreeWalker(Node root,
1052 int whatToShow,
1053 NodeFilter filter,
1054 boolean expandEntities)
1055 {
1056 return new DomNodeIterator(root, whatToShow, filter, expandEntities,
1057 true);
1058 }
1059
1060 // DOM Level 3 methods
1061
1062 /**
1063 * DOM L3
1064 */
1065 public String getInputEncoding()
1066 {
1067 return inputEncoding;
1068 }
1069
1070 public void setInputEncoding(String inputEncoding)
1071 {
1072 this.inputEncoding = inputEncoding;
1073 }
1074
1075 /**
1076 * DOM L3
1077 */
1078 public String getXmlEncoding()
1079 {
1080 return encoding;
1081 }
1082
1083 public void setXmlEncoding(String encoding)
1084 {
1085 this.encoding = encoding;
1086 }
1087
1088 public boolean getXmlStandalone()
1089 {
1090 return standalone;
1091 }
1092
1093 public void setXmlStandalone(boolean xmlStandalone)
1094 {
1095 standalone = xmlStandalone;
1096 }
1097
1098 public String getXmlVersion()
1099 {
1100 return version;
1101 }
1102
1103 public void setXmlVersion(String xmlVersion)
1104 {
1105 if (xmlVersion == null)
1106 {
1107 xmlVersion = "1.0";
1108 }
1109 if ("1.0".equals(xmlVersion) ||
1110 "1.1".equals(xmlVersion))
1111 {
1112 version = xmlVersion;
1113 }
1114 else
1115 {
1116 throw new DomDOMException(DOMException.NOT_SUPPORTED_ERR);
1117 }
1118 }
1119
1120 public boolean getStrictErrorChecking()
1121 {
1122 return checkingCharacters;
1123 }
1124
1125 public void setStrictErrorChecking(boolean strictErrorChecking)
1126 {
1127 checkingCharacters = strictErrorChecking;
1128 }
1129
1130 public String lookupPrefix(String namespaceURI)
1131 {
1132 Node root = getDocumentElement();
1133 return (root == null) ? null : root.lookupPrefix(namespaceURI);
1134 }
1135
1136 public boolean isDefaultNamespace(String namespaceURI)
1137 {
1138 Node root = getDocumentElement();
1139 return (root == null) ? false : root.isDefaultNamespace(namespaceURI);
1140 }
1141
1142 public String lookupNamespaceURI(String prefix)
1143 {
1144 Node root = getDocumentElement();
1145 return (root == null) ? null : root.lookupNamespaceURI(prefix);
1146 }
1147
1148 public String getBaseURI()
1149 {
1150 return getDocumentURI();
1151 /*
1152 Node root = getDocumentElement();
1153 if (root != null)
1154 {
1155 NamedNodeMap attrs = root.getAttributes();
1156 Node xmlBase = attrs.getNamedItemNS(XMLConstants.XML_NS_URI, "base");
1157 if (xmlBase != null)
1158 {
1159 return xmlBase.getNodeValue();
1160 }
1161 }
1162 return systemId;
1163 */
1164 }
1165
1166 public String getDocumentURI()
1167 {
1168 return systemId;
1169 }
1170
1171 public void setDocumentURI(String documentURI)
1172 {
1173 systemId = documentURI;
1174 }
1175
1176 public Node adoptNode(Node source)
1177 {
1178 int sourceNodeType = source.getNodeType();
1179 switch (sourceNodeType)
1180 {
1181 case DOCUMENT_NODE:
1182 case DOCUMENT_TYPE_NODE:
1183 throw new DomDOMException(DOMException.NOT_SUPPORTED_ERR);
1184 case ENTITY_NODE:
1185 case NOTATION_NODE:
1186 throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR);
1187 }
1188 if (source instanceof DomNode)
1189 {
1190 // GNU native
1191 DomNode src = (DomNode) source;
1192 DomNode dst = src;
1193 if (dst.parent != null)
1194 {
1195 dst = (DomNode) dst.cloneNode(true);
1196 }
1197 dst.setOwner(this);
1198 src.notifyUserDataHandlers(UserDataHandler.NODE_ADOPTED, src, dst);
1199 return dst;
1200 }
1201 else
1202 {
1203 // Some other implementation
1204 Node dst = null;
1205 switch (sourceNodeType)
1206 {
1207 case Node.ATTRIBUTE_NODE:
1208 {
1209 Attr src = (Attr) source;
1210 String nodeName = src.getNodeName();
1211 String localName = src.getLocalName();
1212 String namespaceUri = src.getNamespaceURI();
1213 dst = (localName == null) ?
1214 createAttribute(nodeName) :
1215 createAttributeNS(namespaceUri, nodeName);
1216 adoptChildren(src, dst);
1217 break;
1218 }
1219 case Node.CDATA_SECTION_NODE:
1220 {
1221 CDATASection src = (CDATASection) source;
1222 dst = createCDATASection(src.getData());
1223 break;
1224 }
1225 case Node.COMMENT_NODE:
1226 {
1227 Comment src = (Comment) source;
1228 dst = createComment(src.getData());
1229 break;
1230 }
1231 case Node.DOCUMENT_FRAGMENT_NODE:
1232 {
1233 DocumentFragment src = (DocumentFragment) source;
1234 dst = createDocumentFragment();
1235 adoptChildren(src, dst);
1236 break;
1237 }
1238 case Node.ELEMENT_NODE:
1239 {
1240 Element src = (Element) source;
1241 String nodeName = src.getNodeName();
1242 String localName = src.getLocalName();
1243 String namespaceUri = src.getNamespaceURI();
1244 dst = (localName == null) ?
1245 createElement(nodeName) :
1246 createElementNS(namespaceUri, nodeName);
1247 adoptAttributes(src, dst);
1248 adoptChildren(src, dst);
1249 break;
1250 }
1251 case Node.ENTITY_REFERENCE_NODE:
1252 {
1253 EntityReference src = (EntityReference) source;
1254 dst = createEntityReference(src.getNodeName());
1255 adoptChildren(src, dst);
1256 break;
1257 }
1258 case Node.PROCESSING_INSTRUCTION_NODE:
1259 {
1260 ProcessingInstruction src = (ProcessingInstruction) source;
1261 dst = createProcessingInstruction(src.getTarget(),
1262 src.getData());
1263 break;
1264 }
1265 case Node.TEXT_NODE:
1266 {
1267 Text src = (Text) source;
1268 dst = createTextNode(src.getData());
1269 break;
1270 }
1271 }
1272 return dst;
1273 }
1274 }
1275
1276 void adoptChildren(Node src, Node dst)
1277 {
1278 Node node = src.getFirstChild();
1279 while (node != null)
1280 {
1281 Node next = node.getNextSibling();
1282 dst.appendChild(adoptNode(node));
1283 node = next;
1284 }
1285 }
1286
1287 void adoptAttributes(Node src, Node dst)
1288 {
1289 NamedNodeMap srcAttrs = src.getAttributes();
1290 NamedNodeMap dstAttrs = dst.getAttributes();
1291 int len = srcAttrs.getLength();
1292 for (int i = 0; i < len; i++)
1293 {
1294 Node node = srcAttrs.item(i);
1295 String localName = node.getLocalName();
1296 if (localName == null)
1297 {
1298 dstAttrs.setNamedItem(adoptNode(node));
1299 }
1300 else
1301 {
1302 dstAttrs.setNamedItemNS(adoptNode(node));
1303 }
1304 }
1305 }
1306
1307 public DOMConfiguration getDomConfig()
1308 {
1309 if (config == null)
1310 {
1311 config = new DomDocumentConfiguration();
1312 }
1313 return config;
1314 }
1315
1316 public void normalizeDocument()
1317 {
1318 boolean save = building;
1319 building = true;
1320 normalizeNode(this);
1321 building = save;
1322 }
1323
1324 void normalizeNode(DomNode node)
1325 {
1326 node.normalize();
1327 if (config != null)
1328 {
1329 switch (node.nodeType)
1330 {
1331 case CDATA_SECTION_NODE:
1332 if (!config.cdataSections)
1333 {
1334 // replace CDATA section with text node
1335 Text text = createTextNode(node.getNodeValue());
1336 node.parent.insertBefore(text, node);
1337 node.parent.removeChild(node);
1338 // merge adjacent text nodes
1339 String data = text.getWholeText();
1340 node = (DomNode) text.replaceWholeText(data);
1341 }
1342 else if (config.splitCdataSections)
1343 {
1344 String value = node.getNodeValue();
1345 int i = value.indexOf("]]>");
1346 while (i != -1)
1347 {
1348 Node node2 = createCDATASection(value.substring(0, i));
1349 node.parent.insertBefore(node2, node);
1350 value = value.substring(i + 3);
1351 node.setNodeValue(value);
1352 i = value.indexOf("]]>");
1353 }
1354 }
1355 break;
1356 case COMMENT_NODE:
1357 if (!config.comments)
1358 {
1359 node.parent.removeChild(node);
1360 }
1361 break;
1362 case TEXT_NODE:
1363 if (!config.elementContentWhitespace &&
1364 ((Text) node).isElementContentWhitespace())
1365 {
1366 node.parent.removeChild(node);
1367 }
1368 break;
1369 case ENTITY_REFERENCE_NODE:
1370 if (!config.entities)
1371 {
1372 for (DomNode ctx = node.first; ctx != null; )
1373 {
1374 DomNode ctxNext = ctx.next;
1375 node.parent.insertBefore(ctx, node);
1376 ctx = ctxNext;
1377 }
1378 node.parent.removeChild(node);
1379 }
1380 break;
1381 case ELEMENT_NODE:
1382 if (!config.namespaceDeclarations)
1383 {
1384 DomNamedNodeMap attrs =
1385 (DomNamedNodeMap) node.getAttributes();
1386 boolean aro = attrs.readonly;
1387 attrs.readonly = false; // Ensure we can delete if necessary
1388 int len = attrs.getLength();
1389 for (int i = 0; i < len; i++)
1390 {
1391 Node attr = attrs.item(i);
1392 String namespace = attr.getNamespaceURI();
1393 if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespace))
1394 {
1395 attrs.removeNamedItemNS(namespace,
1396 attr.getLocalName());
1397 i--;
1398 len--;
1399 }
1400 }
1401 attrs.readonly = aro;
1402 }
1403 break;
1404 }
1405 }
1406 for (DomNode ctx = node.first; ctx != null; )
1407 {
1408 DomNode ctxNext = ctx.next;
1409 normalizeNode(ctx);
1410 ctx = ctxNext;
1411 }
1412 }
1413
1414 public Node renameNode(Node n, String namespaceURI, String qualifiedName)
1415 throws DOMException
1416 {
1417 if (n instanceof DomNsNode)
1418 {
1419 DomNsNode src = (DomNsNode) n;
1420 if (src == null)
1421 {
1422 throw new DomDOMException(DOMException.NOT_FOUND_ERR);
1423 }
1424 if (src.owner != this)
1425 {
1426 throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
1427 null, src, 0);
1428 }
1429 boolean xml11 = "1.1".equals(version);
1430 checkName(qualifiedName, xml11);
1431 int ci = qualifiedName.indexOf(':');
1432 if ("".equals(namespaceURI))
1433 {
1434 namespaceURI = null;
1435 }
1436 if (namespaceURI != null)
1437 {
1438 checkNCName(qualifiedName, xml11);
1439 String prefix = (ci == -1) ? "" :
1440 qualifiedName.substring(0, ci);
1441 if (XMLConstants.XML_NS_PREFIX.equals(prefix) &&
1442 !XMLConstants.XML_NS_URI.equals(namespaceURI))
1443 {
1444 throw new DomDOMException(DOMException.NAMESPACE_ERR,
1445 "xml namespace must be " +
1446 XMLConstants.XML_NS_URI, src, 0);
1447 }
1448 else if (src.nodeType == ATTRIBUTE_NODE &&
1449 (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix) ||
1450 XMLConstants.XMLNS_ATTRIBUTE.equals(qualifiedName)) &&
1451 !XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI))
1452 {
1453 throw new DomDOMException(DOMException.NAMESPACE_ERR,
1454 "xmlns namespace must be " +
1455 XMLConstants.XMLNS_ATTRIBUTE_NS_URI, src, 0);
1456 }
1457 if (XMLConstants.XML_NS_URI.equals(namespaceURI) &&
1458 !XMLConstants.XML_NS_PREFIX.equals(prefix))
1459 {
1460 throw new DomDOMException(DOMException.NAMESPACE_ERR,
1461 "xml namespace must be " +
1462 XMLConstants.XML_NS_URI, src, 0);
1463 }
1464 else if (src.nodeType == ATTRIBUTE_NODE &&
1465 XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI) &&
1466 !(XMLConstants.XMLNS_ATTRIBUTE.equals(prefix) ||
1467 XMLConstants.XMLNS_ATTRIBUTE.equals(qualifiedName)))
1468 {
1469 throw new DomDOMException(DOMException.NAMESPACE_ERR,
1470 "xmlns namespace must be " +
1471 XMLConstants.XMLNS_ATTRIBUTE_NS_URI, src, 0);
1472 }
1473
1474 }
1475 src.setNodeName(qualifiedName);
1476 src.setNamespaceURI(namespaceURI);
1477 src.notifyUserDataHandlers(UserDataHandler.NODE_RENAMED, src, src);
1478 // TODO MutationNameEvents
1479 // DOMElementNameChanged or DOMAttributeNameChanged
1480 return src;
1481 }
1482 throw new DomDOMException(DOMException.NOT_SUPPORTED_ERR, null, n, 0);
1483 }
1484
1485 // -- XPathEvaluator --
1486
1487 public XPathExpression createExpression(String expression,
1488 XPathNSResolver resolver)
1489 throws XPathException, DOMException
1490 {
1491 return new DomXPathExpression(this, expression, resolver);
1492 }
1493
1494 public XPathNSResolver createNSResolver(Node nodeResolver)
1495 {
1496 return new DomXPathNSResolver(nodeResolver);
1497 }
1498
1499 public Object evaluate(String expression,
1500 Node contextNode,
1501 XPathNSResolver resolver,
1502 short type,
1503 Object result)
1504 throws XPathException, DOMException
1505 {
1506 XPathExpression xpe =
1507 new DomXPathExpression(this, expression, resolver);
1508 return xpe.evaluate(contextNode, type, result);
1509 }
1510
1511 }
1512