1. /*
  2. * $Id: AttributeSet.java,v 1.15 2001/07/13 00:00:30 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.Writer;
  61. import java.io.IOException;
  62. import java.util.Vector;
  63. import org.w3c.dom.*;
  64. import org.xml.sax.Attributes;
  65. import org.apache.crimson.parser.AttributesEx;
  66. import org.apache.crimson.util.XmlNames;
  67. /**
  68. * Class representing a list of XML attributes.
  69. *
  70. * <P> This couples slightly with the Sun XML parser, in that it optionally
  71. * uses an extended SAX API to see if an attribute was specified in the
  72. * document or was instead defaulted by attribute processing.
  73. *
  74. * @author David Brownell
  75. * @version $Revision: 1.15 $
  76. */
  77. final
  78. class AttributeSet implements NamedNodeMap, XmlWritable
  79. {
  80. private boolean readonly;
  81. private Vector list;
  82. private Element ownerElement;
  83. private AttributeSet() {
  84. // no-arg constructor
  85. }
  86. /* Constructs an attribute list, with associated name scope. */
  87. // package private
  88. AttributeSet(Element ownerElement) {
  89. list = new Vector (5);
  90. this.ownerElement = ownerElement;
  91. }
  92. /*
  93. * Constructs a copy of an attribute list, for use in cloning.
  94. * AttributeNode.ownerElement is set later when the attributes are
  95. * associated with an Element.
  96. */
  97. // package private
  98. AttributeSet (AttributeSet original, boolean deep)
  99. {
  100. int size = original.getLength ();
  101. list = new Vector (size);
  102. for (int i = 0; i < size; i++) {
  103. Node node = original.item (i);
  104. if (!(node instanceof AttributeNode))
  105. throw new IllegalArgumentException (((NodeBase)node).
  106. getMessage ("A-003"));
  107. AttributeNode attr = (AttributeNode)node;
  108. node = attr.cloneAttributeNode(deep);
  109. list.addElement (node);
  110. }
  111. }
  112. /**
  113. * Constructor used to implement Document.importNode(). Only
  114. * "specified" Attr nodes are copied. Copy is always deep.
  115. */
  116. AttributeSet(AttributeSet original) {
  117. int size = original.getLength();
  118. list = new Vector(size);
  119. for (int i = 0; i < size; i++) {
  120. Node node = original.item(i);
  121. if (!(node instanceof AttributeNode)) {
  122. throw new IllegalArgumentException(((NodeBase)node).
  123. getMessage ("A-003"));
  124. }
  125. AttributeNode attr = (AttributeNode) node;
  126. // Copy only specified attributes
  127. if (attr.getSpecified()) {
  128. node = attr.cloneAttributeNode(true);
  129. list.addElement(node);
  130. }
  131. }
  132. list.trimToSize();
  133. }
  134. /**
  135. * Create a DOM NamedNodeMap consisting of DOM Level 2 Attr nodes from
  136. * a SAX2 Attributes object
  137. */
  138. static AttributeSet createAttributeSet2(Attributes source)
  139. throws DOMException
  140. {
  141. AttributeSet retval = new AttributeSet();
  142. int len = source.getLength();
  143. AttributesEx ex = null;
  144. retval.list = new Vector(len);
  145. if (source instanceof AttributesEx) {
  146. ex = (AttributesEx) source;
  147. }
  148. for (int i = 0; i < len; i++) {
  149. // Process the namespaceURI according to DOM Level 2 spec
  150. String uri;
  151. String qName = source.getQName(i);
  152. if ("xmlns".equals(qName)
  153. || "xmlns".equals(XmlNames.getPrefix(qName))) {
  154. // Associate the right namespaceURI with "xmlns" attributes
  155. uri = XmlNames.SPEC_XMLNS_URI;
  156. } else {
  157. uri = source.getURI(i);
  158. // Translate "" of SAX2 to null. See DOM2 spec under Node
  159. // namespaceURI
  160. if ("".equals(uri)) {
  161. uri = null;
  162. }
  163. }
  164. AttributeNode attrNode =
  165. new AttributeNode(uri, qName,
  166. source.getValue(i),
  167. ex == null // remember if it was specified
  168. ? true
  169. : ex.isSpecified(i),
  170. ex == null // remember any default value
  171. ? null
  172. : ex.getDefault(i));
  173. retval.list.addElement(attrNode);
  174. }
  175. return retval;
  176. }
  177. /**
  178. * Create a DOM NamedNodeMap consisting of DOM Level 1 Attr nodes from
  179. * a SAX2 Attributes object
  180. */
  181. static AttributeSet createAttributeSet1(Attributes source)
  182. throws DOMException
  183. {
  184. AttributeSet retval = new AttributeSet();
  185. int len = source.getLength();
  186. AttributesEx ex = null;
  187. retval.list = new Vector(len);
  188. if (source instanceof AttributesEx) {
  189. ex = (AttributesEx) source;
  190. }
  191. for (int i = 0; i < len; i++) {
  192. AttributeNode1 attrNode1 = new AttributeNode1(
  193. source.getQName(i),
  194. source.getValue(i),
  195. ex == null // remember if it was specified
  196. ? true
  197. : ex.isSpecified(i),
  198. ex == null // remember any default value
  199. ? null
  200. : ex.getDefault(i));
  201. retval.list.addElement(attrNode1);
  202. }
  203. return retval;
  204. }
  205. // package private
  206. void trimToSize () { list.trimToSize (); }
  207. // package private
  208. public void setReadonly ()
  209. {
  210. readonly = true;
  211. for (int i = 0; i < list.size (); i++)
  212. ((AttributeNode)list.elementAt (i)).setReadonly (true);
  213. }
  214. public boolean isReadonly () {
  215. if (readonly)
  216. return true;
  217. for (int i = 0; i < list.size (); i++) {
  218. if (((AttributeNode)list.elementAt (i)).isReadonly ()) {
  219. return true;
  220. }
  221. }
  222. return false;
  223. }
  224. // package private
  225. void setOwnerElement(Element e) {
  226. if (e != null && ownerElement != null) {
  227. throw new IllegalStateException(((NodeBase)e).getMessage("A-004"));
  228. }
  229. ownerElement = e;
  230. // need to bind the attributes to this element
  231. int length = list.size();
  232. for (int i = 0; i < length; i++) {
  233. AttributeNode node;
  234. node = (AttributeNode)list.elementAt(i);
  235. node.setOwnerElement(null);
  236. node.setOwnerElement(e);
  237. }
  238. }
  239. // package private
  240. String getValue (String name)
  241. {
  242. Attr attr = (Attr) getNamedItem (name);
  243. if (attr == null)
  244. return "";
  245. else
  246. return attr.getValue ();
  247. }
  248. public Node getNamedItem (String name)
  249. {
  250. int length = list.size ();
  251. Node value;
  252. for (int i = 0; i < length; i++) {
  253. value = item (i);
  254. if (value.getNodeName ().equals (name))
  255. return value;
  256. }
  257. return null;
  258. }
  259. /**
  260. * <b>DOM2:</b>
  261. */
  262. public Node getNamedItemNS(String namespaceURI, String localName) {
  263. // DOM L2 spec specifies that Attr.localName is null for L1 created
  264. // Attr and Element nodes, therefore this method cannot be used to
  265. // lookup such a node.
  266. if (localName == null) {
  267. return null;
  268. }
  269. for (int i = 0; i < list.size(); i++) {
  270. Node value = item(i);
  271. String iLocalName = value.getLocalName();
  272. if (localName.equals(iLocalName)) {
  273. String iNamespaceURI = value.getNamespaceURI();
  274. if (namespaceURI == iNamespaceURI ||
  275. (namespaceURI != null
  276. && namespaceURI.equals(iNamespaceURI))) {
  277. return value;
  278. }
  279. }
  280. }
  281. return null;
  282. }
  283. public int getLength ()
  284. {
  285. return list.size ();
  286. }
  287. public Node item (int index)
  288. {
  289. if (index < 0 || index >= list.size ())
  290. return null;
  291. return (Node) list.elementAt (index);
  292. }
  293. public Node removeNamedItem(String name)
  294. throws DOMException
  295. {
  296. if (readonly) {
  297. throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
  298. }
  299. for (int i = 0; i < list.size(); i++) {
  300. Node value = (Node)list.elementAt(i);
  301. if (value.getNodeName().equals(name)) {
  302. // Found a match
  303. list.removeElementAt(i);
  304. AttributeNode attr = (AttributeNode)value;
  305. // Replace with Attr node of default value if it has one
  306. String defaultValue = attr.getDefaultValue();
  307. if (defaultValue != null) {
  308. AttributeNode newAttr = attr.cloneAttributeNode(true);
  309. newAttr.setOwnerElement(attr.getOwnerElement());
  310. newAttr.setValue(defaultValue);
  311. newAttr.setSpecified(false);
  312. list.addElement(newAttr);
  313. }
  314. // Set the ownerElement of attr to null since we're
  315. // removing it
  316. attr.setOwnerElement(null);
  317. return attr;
  318. }
  319. }
  320. throw new DomEx(DomEx.NOT_FOUND_ERR);
  321. }
  322. /**
  323. * <b>DOM2:</b>
  324. */
  325. public Node removeNamedItemNS(String namespaceURI, String localName)
  326. throws DOMException
  327. {
  328. if (readonly) {
  329. throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
  330. }
  331. // See comments for getNamedItemNS() for why localName cannot be null
  332. if (localName == null) {
  333. throw new DomEx(DomEx.NOT_FOUND_ERR);
  334. }
  335. for (int i = 0; i < list.size(); i++) {
  336. Node value = (Node)list.elementAt(i);
  337. String iLocalName = value.getLocalName();
  338. if (localName.equals(iLocalName)) {
  339. String iNamespaceURI = value.getNamespaceURI();
  340. if (namespaceURI == iNamespaceURI ||
  341. (namespaceURI != null
  342. && namespaceURI.equals(iNamespaceURI))) {
  343. // Found a match
  344. list.removeElementAt(i);
  345. AttributeNode attr = (AttributeNode)value;
  346. // Replace with Attr node of default value if it has one
  347. String defaultValue = attr.getDefaultValue();
  348. if (defaultValue != null) {
  349. AttributeNode newAttr = attr.cloneAttributeNode(true);
  350. newAttr.setOwnerElement(attr.getOwnerElement());
  351. newAttr.setValue(defaultValue);
  352. newAttr.setSpecified(false);
  353. list.addElement(newAttr);
  354. }
  355. // Set the ownerElement of attr to null since we're
  356. // removing it
  357. attr.setOwnerElement(null);
  358. return attr;
  359. }
  360. }
  361. }
  362. throw new DomEx(DomEx.NOT_FOUND_ERR);
  363. }
  364. /**
  365. * Note: this method both checks and sets the "ownerElement" of the
  366. * "value" parameter. So if "ownerElement" is already set, an
  367. * incorrect error results. Callers should avoid setting
  368. * "ownerElement".
  369. */
  370. public Node setNamedItem(Node value)
  371. throws DOMException
  372. {
  373. if (readonly) {
  374. throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
  375. }
  376. if (!(value instanceof AttributeNode) ||
  377. value.getOwnerDocument() != ownerElement.getOwnerDocument()) {
  378. throw new DomEx(DomEx.WRONG_DOCUMENT_ERR);
  379. }
  380. AttributeNode att = (AttributeNode)value;
  381. if (att.getOwnerElement() != null) {
  382. throw new DomEx(DomEx.INUSE_ATTRIBUTE_ERR);
  383. }
  384. int length = list.size();
  385. AttributeNode oldAtt;
  386. for (int i = 0; i < length; i++) {
  387. oldAtt = (AttributeNode)item(i);
  388. if (oldAtt.getNodeName().equals(value.getNodeName())) {
  389. if (oldAtt.isReadonly()) {
  390. throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
  391. }
  392. att.setOwnerElement(ownerElement);
  393. list.setElementAt(att, i);
  394. oldAtt.setOwnerElement(null);
  395. return oldAtt;
  396. }
  397. }
  398. att.setOwnerElement(ownerElement);
  399. list.addElement(value);
  400. return null;
  401. }
  402. /**
  403. * <b>DOM2:</b>
  404. * Spec technically allows other types of nodes also, but this code
  405. * assumes Attr nodes only
  406. */
  407. public Node setNamedItemNS(Node arg) throws DOMException {
  408. if (readonly) {
  409. throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
  410. }
  411. if (!(arg instanceof AttributeNode) ||
  412. arg.getOwnerDocument() != ownerElement.getOwnerDocument()) {
  413. throw new DomEx(DomEx.WRONG_DOCUMENT_ERR);
  414. }
  415. AttributeNode attr = (AttributeNode) arg;
  416. if (attr.getOwnerElement() != null) {
  417. throw new DomEx(DomEx.INUSE_ATTRIBUTE_ERR);
  418. }
  419. // Both localName and namespaceURI can be null for Attr nodes
  420. // created by DOM Level 1 methods
  421. String localName = attr.getLocalName();
  422. String namespaceURI = attr.getNamespaceURI();
  423. int length = list.size();
  424. for (int i = 0; i < length; i++) {
  425. AttributeNode oldNode = (AttributeNode) item(i);
  426. String iLocalName = oldNode.getLocalName();
  427. String iNamespaceURI = oldNode.getNamespaceURI();
  428. if ((localName == iLocalName ||
  429. (localName != null && localName.equals(iLocalName)))
  430. && (namespaceURI == iNamespaceURI ||
  431. (namespaceURI != null
  432. && namespaceURI.equals(iNamespaceURI)))) {
  433. // Found a matching node so replace it
  434. if (oldNode.isReadonly()) {
  435. throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
  436. }
  437. attr.setOwnerElement(ownerElement);
  438. list.setElementAt(attr, i);
  439. oldNode.setOwnerElement(null);
  440. return oldNode;
  441. }
  442. }
  443. // Append instead of replace
  444. attr.setOwnerElement(ownerElement);
  445. list.addElement(attr);
  446. return null;
  447. }
  448. /**
  449. * Writes out the attribute list. Attributes known to have been
  450. * derived from the DTD are not (at this time) written out. Part
  451. * of writing standalone XML is first ensuring that all attributes
  452. * are flagged as being specified in the "printed" form (or else
  453. * are defaulted only in the internal DTD subset).
  454. */
  455. public void writeXml (XmlWriteContext context) throws IOException
  456. {
  457. Writer out = context.getWriter ();
  458. int length = list.size ();
  459. AttributeNode tmp;
  460. for (int i = 0; i < length; i++) {
  461. tmp = (AttributeNode) list.elementAt (i);
  462. if (tmp.getSpecified ()) {
  463. out.write (' ');
  464. tmp.writeXml (context);
  465. }
  466. }
  467. }
  468. /**
  469. * Does nothing; this type of node has no children.
  470. */
  471. public void writeChildrenXml (XmlWriteContext context) throws IOException
  472. {
  473. }
  474. public String toString ()
  475. {
  476. try {
  477. CharArrayWriter w = new CharArrayWriter ();
  478. XmlWriteContext x = new XmlWriteContext (w);
  479. writeXml (x);
  480. return w.toString ();
  481. } catch (IOException e) {
  482. return super.toString ();
  483. }
  484. }
  485. }