1. /*
  2. * $Id: ElementNode2.java,v 1.11 2001/04/11 02:52:37 edwingo Exp $
  3. *
  4. * The Apache Software License, Version 1.1
  5. *
  6. *
  7. * Copyright (c) 2000 The Apache Software Foundation. All rights
  8. * reserved.
  9. *
  10. * Redistribution and use in source and binary forms, with or without
  11. * modification, are permitted provided that the following conditions
  12. * are met:
  13. *
  14. * 1. Redistributions of source code must retain the above copyright
  15. * notice, this list of conditions and the following disclaimer.
  16. *
  17. * 2. Redistributions in binary form must reproduce the above copyright
  18. * notice, this list of conditions and the following disclaimer in
  19. * the documentation and/or other materials provided with the
  20. * distribution.
  21. *
  22. * 3. The end-user documentation included with the redistribution,
  23. * if any, must include the following acknowledgment:
  24. * "This product includes software developed by the
  25. * Apache Software Foundation (http://www.apache.org/)."
  26. * Alternately, this acknowledgment may appear in the software itself,
  27. * if and wherever such third-party acknowledgments normally appear.
  28. *
  29. * 4. The names "Crimson" and "Apache Software Foundation" must
  30. * not be used to endorse or promote products derived from this
  31. * software without prior written permission. For written
  32. * permission, please contact apache@apache.org.
  33. *
  34. * 5. Products derived from this software may not be called "Apache",
  35. * nor may "Apache" appear in their name, without prior written
  36. * permission of the Apache Software Foundation.
  37. *
  38. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  39. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  40. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  41. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  42. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  43. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  44. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  45. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  46. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  47. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  48. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  49. * SUCH DAMAGE.
  50. * ====================================================================
  51. *
  52. * This software consists of voluntary contributions made by many
  53. * individuals on behalf of the Apache Software Foundation and was
  54. * originally based on software copyright (c) 1999, Sun Microsystems, Inc.,
  55. * http://www.sun.com. For more information on the Apache Software
  56. * Foundation, please see <http://www.apache.org/>.
  57. */
  58. package org.apache.crimson.tree;
  59. import java.io.CharArrayWriter;
  60. import java.io.IOException;
  61. import java.io.Writer;
  62. import java.util.Enumeration;
  63. import org.apache.crimson.util.XmlNames;
  64. import org.w3c.dom.*;
  65. /**
  66. * Modified version of ElementNode to support DOM Level 2 methods. This
  67. * class is named ElementNode2 for backward compatibility since old DOM
  68. * Level 1 apps may have subclassed ElementNode.
  69. *
  70. * This class represents XML elements in a parse tree, and is often
  71. * subclassed to add custom behaviors. When an XML Document object
  72. * is built using an <em>XmlDocumentBuilder</em> instance, simple
  73. * declarative configuration information may be used to control whether
  74. * this class, or some specialized subclass (e.g. supporting HTML DOM
  75. * methods) is used for elements in the resulting tree.
  76. *
  77. * <P> As well as defining new methods to provide behaviors which are
  78. * specific to application frameworks, such as Servlets or Swing, such
  79. * subclasses may also override methods such as <em>doneParse</em>
  80. * and <em>appendChild</em> to perform some kinds of processing during
  81. * tree construction. Such processing can include transforming tree
  82. * structure to better suit the needs of a given application. When
  83. * such transformation is done, the <em>XmlWritable</em> methods
  84. * may need to be overridden to make elements transform themselves back
  85. * to XML without losing information. (One common transformation is
  86. * eliminating redundant representations of data; attributes of an XML
  87. * element may correspond to defaultable object properties, and so on.)
  88. *
  89. * <P> Element nodes also support a single <em>userObject</em> property,
  90. * which may be used to bind objects to elements where subclassing is
  91. * either not possible or is inappropriate. For example, user interface
  92. * objects often derive from <code>java.awt.Component</code>, so that
  93. * they can't extend a different class (<em>ElementNode</em>).
  94. *
  95. * @see XmlDocumentBuilder
  96. *
  97. * @author David Brownell
  98. * @author Edwin Goei
  99. */
  100. public class ElementNode2 extends NamespacedNode implements ElementEx
  101. {
  102. protected AttributeSet attributes;
  103. private String idAttributeName;
  104. private Object userObject;
  105. private static final char tagStart [] = { '<', '/' };
  106. private static final char tagEnd [] = { ' ', '/', '>' };
  107. public ElementNode2(String namespaceURI, String qName)
  108. throws DomEx
  109. {
  110. super(namespaceURI, qName);
  111. }
  112. /**
  113. * Make a clone of this node and return it. Used for cloneNode().
  114. */
  115. ElementNode2 makeClone() {
  116. ElementNode2 retval = new ElementNode2(namespaceURI, qName);
  117. if (attributes != null) {
  118. retval.attributes = new AttributeSet(attributes, true);
  119. retval.attributes.setOwnerElement(retval);
  120. }
  121. retval.idAttributeName = idAttributeName;
  122. retval.userObject = userObject;
  123. retval.ownerDocument = ownerDocument;
  124. return retval;
  125. }
  126. /**
  127. * @return New ElementNode2 which is a copy of "this" but without
  128. * attributes that are defaulted in the original document.
  129. *
  130. * Used to implement Document.importNode().
  131. */
  132. ElementNode2 createCopyForImportNode(boolean deep) {
  133. ElementNode2 retval = new ElementNode2(namespaceURI, qName);
  134. if (attributes != null) {
  135. // Copy only "specified" Attr-s
  136. retval.attributes = new AttributeSet(attributes);
  137. retval.attributes.setOwnerElement(retval);
  138. }
  139. retval.userObject = userObject;
  140. if (deep) {
  141. // Copy ownerDocument to prevent appendChild() from throwing
  142. // WRONG_DOCUMENT_ERR for deep copies. This gets changed to
  143. // the correct ownerDocument later in Document.importNode().
  144. retval.ownerDocument = ownerDocument;
  145. for (int i = 0; true; i++) {
  146. Node node = item(i);
  147. if (node == null) {
  148. break;
  149. }
  150. if (node instanceof ElementNode2) {
  151. retval.appendChild(
  152. ((ElementNode2) node).createCopyForImportNode(true));
  153. } else {
  154. retval.appendChild(node.cloneNode(true));
  155. }
  156. }
  157. }
  158. return retval;
  159. }
  160. static void checkArguments(String namespaceURI, String qualifiedName)
  161. throws DomEx
  162. {
  163. // [6] QName ::= (Prefix ':')? LocalPart
  164. // [7] Prefix ::= NCName
  165. // [8] LocalPart ::= NCName
  166. if (qualifiedName == null) {
  167. throw new DomEx(DomEx.NAMESPACE_ERR);
  168. }
  169. int first = qualifiedName.indexOf(':');
  170. if (first <= 0) {
  171. // no Prefix, only check LocalPart
  172. if (!XmlNames.isUnqualifiedName(qualifiedName)) {
  173. throw new DomEx(DomEx.INVALID_CHARACTER_ERR);
  174. }
  175. return;
  176. }
  177. // Prefix exists, check everything
  178. int last = qualifiedName.lastIndexOf(':');
  179. if (last != first) {
  180. throw new DomEx(DomEx.NAMESPACE_ERR);
  181. }
  182. String prefix = qualifiedName.substring(0, first);
  183. String localName = qualifiedName.substring(first + 1);
  184. if (!XmlNames.isUnqualifiedName(prefix)
  185. || !XmlNames.isUnqualifiedName(localName)) {
  186. throw new DomEx(DomEx.INVALID_CHARACTER_ERR);
  187. }
  188. // If we get here then we must have a valid prefix
  189. if (namespaceURI == null
  190. || (prefix.equals("xml") &&
  191. !XmlNames.SPEC_XML_URI.equals(namespaceURI))) {
  192. throw new DomEx(DomEx.NAMESPACE_ERR);
  193. }
  194. }
  195. public void trimToSize ()
  196. {
  197. super.trimToSize ();
  198. if (attributes != null)
  199. attributes.trimToSize ();
  200. }
  201. // Assigns the element's attributes.
  202. void setAttributes (AttributeSet a)
  203. {
  204. AttributeSet oldAtts = attributes;
  205. // Check if the current AttributeSet or any attribute is readonly
  206. // isReadonly checks if any of the attributes in the AttributeSet
  207. // is readonly..
  208. if (oldAtts != null && oldAtts.isReadonly()) {
  209. throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
  210. }
  211. if (a != null) {
  212. a.setOwnerElement(this);
  213. }
  214. attributes = a;
  215. if (oldAtts != null) {
  216. oldAtts.setOwnerElement(null);
  217. }
  218. }
  219. // package private -- overrides base class method
  220. void checkChildType (int type)
  221. throws DOMException
  222. {
  223. switch (type) {
  224. case ELEMENT_NODE:
  225. case TEXT_NODE:
  226. case COMMENT_NODE:
  227. case PROCESSING_INSTRUCTION_NODE:
  228. case CDATA_SECTION_NODE:
  229. case ENTITY_REFERENCE_NODE:
  230. return;
  231. default:
  232. throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
  233. }
  234. }
  235. // package private -- overrides base class method
  236. public void setReadonly (boolean deep)
  237. {
  238. if (attributes != null)
  239. attributes.setReadonly ();
  240. super.setReadonly (deep);
  241. }
  242. /** <b>DOM:</b> Returns the attributes of this element. */
  243. public NamedNodeMap getAttributes ()
  244. {
  245. if (attributes == null)
  246. attributes = new AttributeSet (this);
  247. return attributes;
  248. }
  249. /**
  250. * Returns whether this node (if it is an element) has any attributes.
  251. * @since DOM Level 2
  252. */
  253. public boolean hasAttributes() {
  254. return attributes != null;
  255. }
  256. /**
  257. * Returns the element and its content as a string, which includes
  258. * all the markup embedded in this element. If the element is not
  259. * fully constructed, the content will not be an XML tag.
  260. */
  261. public String toString ()
  262. {
  263. try {
  264. CharArrayWriter out = new CharArrayWriter ();
  265. XmlWriteContext x = new XmlWriteContext (out);
  266. writeXml (x);
  267. return out.toString ();
  268. } catch (Exception e) {
  269. return super.toString ();
  270. }
  271. }
  272. /**
  273. * Writes this element and all of its children out, as well
  274. * formed XML.
  275. */
  276. public void writeXml (XmlWriteContext context) throws IOException
  277. {
  278. Writer out = context.getWriter ();
  279. if (qName == null)
  280. throw new IllegalStateException ( getMessage ("EN-002"));
  281. out.write (tagStart, 0, 1); // "<"
  282. out.write (qName);
  283. if (attributes != null)
  284. attributes.writeXml (context);
  285. //
  286. // Write empty nodes as "<EMPTY />" to make sure version 3
  287. // and 4 web browsers can read empty tag output as HTML.
  288. // XML allows "<EMPTY/>" too, of course.
  289. //
  290. if (!hasChildNodes ())
  291. out.write (tagEnd, 0, 3); // " />"
  292. else {
  293. out.write (tagEnd, 2, 1); // ">"
  294. writeChildrenXml (context);
  295. out.write (tagStart, 0, 2); // "</"
  296. out.write (qName);
  297. out.write (tagEnd, 2, 1); // ">"
  298. }
  299. }
  300. /**
  301. * Assigns the name of the element's ID attribute; only one attribute
  302. * may have the ID type. XML supports a kind of validatable internal
  303. * linking using ID attributes, with IDREF attributes identifying
  304. * specific nodes (and IDREFS attributes identifying sets of them).
  305. */
  306. public void setIdAttributeName (String attName)
  307. {
  308. if (readonly)
  309. throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
  310. idAttributeName = attName;
  311. }
  312. /**
  313. * Returns the name of the element's ID attribute, if one is known.
  314. */
  315. public String getIdAttributeName ()
  316. { return idAttributeName; }
  317. public void setUserObject (Object userObject)
  318. { this.userObject = userObject; }
  319. public Object getUserObject ()
  320. { return userObject; }
  321. // DOM support
  322. /** <b>DOM:</b> Returns the ELEMENT_NODE node type. */
  323. public short getNodeType () { return ELEMENT_NODE; }
  324. /** <b>DOM:</b> Returns the name of the XML tag for this element. */
  325. public String getTagName () { return qName; }
  326. /**
  327. * Returns <code>true</code> when an attribute with a given name is
  328. * specified on this element or has a default value, <code>false</code>
  329. * otherwise.
  330. * @since DOM Level 2
  331. */
  332. public boolean hasAttribute(String name) {
  333. return getAttributeNode(name) != null;
  334. }
  335. /**
  336. * Returns <code>true</code> when an attribute with a given local name
  337. * and namespace URI is specified on this element or has a default
  338. * value, <code>false</code> otherwise.
  339. * @since DOM Level 2
  340. */
  341. public boolean hasAttributeNS(String namespaceURI, String localName) {
  342. return getAttributeNodeNS(namespaceURI, localName) != null;
  343. }
  344. /** <b>DOM:</b> Returns the value of the named attribute, or an empty
  345. * string
  346. */
  347. public String getAttribute (String name)
  348. {
  349. return (attributes == null)
  350. ? ""
  351. : attributes.getValue (name);
  352. }
  353. /**
  354. * Retrieves an attribute value by local name and namespace URI.
  355. * @since DOM Level 2
  356. */
  357. public String getAttributeNS(String namespaceURI, String localName) {
  358. if (attributes == null) {
  359. return "";
  360. }
  361. Attr attr = getAttributeNodeNS(namespaceURI, localName);
  362. if (attr == null) {
  363. return "";
  364. }
  365. return attr.getValue();
  366. }
  367. /**
  368. * Retrieves an <code>Attr</code> node by local name and namespace URI.
  369. * @since DOM Level 2
  370. */
  371. public Attr getAttributeNodeNS(String namespaceURI, String localName) {
  372. if (localName == null) {
  373. return null;
  374. }
  375. if (attributes == null) {
  376. return null;
  377. }
  378. for (int i = 0; ; i++) {
  379. AttributeNode attr = (AttributeNode) attributes.item(i);
  380. if (attr == null) {
  381. return null;
  382. }
  383. if (localName.equals(attr.getLocalName())
  384. && (attr.getNamespaceURI() == namespaceURI
  385. || attr.getNamespaceURI().equals(namespaceURI))) {
  386. return attr;
  387. }
  388. }
  389. }
  390. /**
  391. * <b>DOM:</b> Assigns or modifies the value of the specified attribute.
  392. */
  393. public void setAttribute (String name, String value)
  394. throws DOMException
  395. {
  396. NodeBase att; // Common superclass of all Attr nodes
  397. if (readonly)
  398. throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
  399. if (!XmlNames.isName(name)) {
  400. throw new DomEx(DOMException.INVALID_CHARACTER_ERR);
  401. }
  402. if (attributes == null)
  403. attributes = new AttributeSet (this);
  404. if ((att = (NodeBase) attributes.getNamedItem (name)) != null)
  405. att.setNodeValue (value);
  406. else {
  407. att = new AttributeNode1(name, value, true, null);
  408. att.setOwnerDocument ((XmlDocument) getOwnerDocument ());
  409. /* "ownerElement" should be null before calling "setNamedItem" */
  410. attributes.setNamedItem (att);
  411. }
  412. }
  413. /**
  414. * <b>DOM2:</b>
  415. * @since DOM Level 2
  416. */
  417. public void setAttributeNS(String namespaceURI, String qualifiedName,
  418. String value)
  419. throws DOMException
  420. {
  421. AttributeNode.checkArguments(namespaceURI, qualifiedName);
  422. Attr attr = getAttributeNodeNS(namespaceURI,
  423. XmlNames.getLocalPart(qualifiedName));
  424. if (attr == null) {
  425. AttributeNode newAttr = new AttributeNode(namespaceURI,
  426. qualifiedName, value,
  427. true, null);
  428. newAttr.setOwnerDocument((XmlDocument)getOwnerDocument());
  429. setAttributeNodeNS(newAttr);
  430. } else {
  431. attr.setValue(value);
  432. attr.setPrefix(XmlNames.getPrefix(qualifiedName));
  433. }
  434. }
  435. /**
  436. * <b>DOM2:</b>
  437. * @since DOM Level 2
  438. */
  439. public Attr setAttributeNodeNS(Attr newAttr) throws DOMException {
  440. if (readonly) {
  441. throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
  442. }
  443. if (newAttr.getOwnerDocument() != getOwnerDocument()) {
  444. throw new DomEx(DomEx.WRONG_DOCUMENT_ERR);
  445. }
  446. if (attributes == null) {
  447. attributes = new AttributeSet(this);
  448. }
  449. // Note: ownerElement of newAttr is both checked and set in the
  450. // following call to AttributeSet.setNamedItemNS(Node)
  451. return (Attr)attributes.setNamedItemNS(newAttr);
  452. }
  453. /** <b>DOM:</b> Remove the named attribute. */
  454. public void removeAttribute (String name)
  455. throws DOMException
  456. {
  457. if (readonly)
  458. throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
  459. if (attributes == null) {
  460. return;
  461. }
  462. try {
  463. attributes.removeNamedItem (name);
  464. } catch (DOMException x) {
  465. // DOM2 does not allow a NOT_FOUND_ERR exception to be thrown
  466. if (x.code != DOMException.NOT_FOUND_ERR) {
  467. throw x;
  468. }
  469. }
  470. }
  471. /**
  472. * <b>DOM2:</b>
  473. * @since DOM Level 2
  474. */
  475. public void removeAttributeNS(String namespaceURI, String localName)
  476. throws DOMException
  477. {
  478. if (readonly) {
  479. throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
  480. }
  481. try {
  482. attributes.removeNamedItemNS(namespaceURI, localName);
  483. } catch (DOMException x) {
  484. // DOM2 does not allow a NOT_FOUND_ERR exception to be thrown
  485. if (x.code != DOMException.NOT_FOUND_ERR) {
  486. throw x;
  487. }
  488. }
  489. }
  490. /** <b>DOM:</b> returns the attribute */
  491. public Attr getAttributeNode (String name)
  492. {
  493. if (attributes != null)
  494. return (Attr) attributes.getNamedItem (name);
  495. else
  496. return null;
  497. }
  498. /** <b>DOM:</b> assigns the attribute */
  499. public Attr setAttributeNode (Attr newAttr)
  500. throws DOMException
  501. {
  502. if (readonly)
  503. throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
  504. if (!(newAttr instanceof AttributeNode))
  505. throw new DomEx (DomEx.WRONG_DOCUMENT_ERR);
  506. if (attributes == null)
  507. attributes = new AttributeSet (this);
  508. // Note: ownerElement of newAttr is both checked and set in the
  509. // following call to AttributeSet.setNamedItem(Node)
  510. return (Attr) attributes.setNamedItem(newAttr);
  511. }
  512. /** <b>DOM:</b> removes the attribute with the same name as this one */
  513. public Attr removeAttributeNode (Attr oldAttr)
  514. throws DOMException
  515. {
  516. if (isReadonly ())
  517. throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
  518. Attr attr = getAttributeNode (oldAttr.getNodeName ());
  519. if (attr == null)
  520. throw new DomEx (DomEx.NOT_FOUND_ERR);
  521. removeAttribute (attr.getNodeName ());
  522. return attr;
  523. }
  524. /**
  525. * Creates a new unparented node whose attributes are the same as
  526. * this node's attributes; if <em>deep</em> is true, the children
  527. * of this node are cloned as children of the new node.
  528. */
  529. public Node cloneNode (boolean deep)
  530. {
  531. try {
  532. ElementNode2 retval = makeClone();
  533. if (deep) {
  534. for (int i = 0; true; i++) {
  535. Node node = item (i);
  536. if (node == null)
  537. break;
  538. retval.appendChild (node.cloneNode (true));
  539. }
  540. }
  541. return retval;
  542. } catch (DOMException e) {
  543. throw new RuntimeException (getMessage ("EN-001"));
  544. }
  545. }
  546. /**
  547. * Convenience method to construct a non-prettyprinting XML write
  548. * context and call writeXml with it. Subclasses may choose to
  549. * to override this method to generate non-XML text,
  550. *
  551. * @param out where to emit the XML content of this node
  552. */
  553. public void write (Writer out) throws IOException
  554. {
  555. writeXml (new XmlWriteContext (out));
  556. }
  557. }