1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. *
  5. * Copyright (c) 2000-2002 The Apache Software Foundation. All rights
  6. * reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * 1. Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * 3. The end-user documentation included with the redistribution,
  21. * if any, must include the following acknowledgment:
  22. * "This product includes software developed by the
  23. * Apache Software Foundation (http://www.apache.org/)."
  24. * Alternately, this acknowledgment may appear in the software itself,
  25. * if and wherever such third-party acknowledgments normally appear.
  26. *
  27. * 4. The names "Xerces" and "Apache Software Foundation" must
  28. * not be used to endorse or promote products derived from this
  29. * software without prior written permission. For written
  30. * permission, please contact apache@apache.org.
  31. *
  32. * 5. Products derived from this software may not be called "Apache",
  33. * nor may "Apache" appear in their name, without prior written
  34. * permission of the Apache Software Foundation.
  35. *
  36. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  37. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  38. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  39. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  40. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  41. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  42. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  43. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  44. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  45. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  46. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  47. * SUCH DAMAGE.
  48. * ====================================================================
  49. *
  50. * This software consists of voluntary contributions made by many
  51. * individuals on behalf of the Apache Software Foundation and was
  52. * originally based on software copyright (c) 1999, International
  53. * Business Machines, Inc., http://www.apache.org. For more
  54. * information on the Apache Software Foundation, please see
  55. * <http://www.apache.org/>.
  56. */
  57. package com.sun.org.apache.xerces.internal.dom;
  58. import java.util.Vector;
  59. import org.w3c.dom.DOMException;
  60. import org.w3c.dom.Node;
  61. /**
  62. * AttributeMap inherits from NamedNodeMapImpl and extends it to deal with the
  63. * specifics of storing attributes. These are:
  64. * <ul>
  65. * <li>managing ownership of attribute nodes
  66. * <li>managing default attributes
  67. * <li>firing mutation events
  68. * </ul>
  69. * <p>
  70. * This class doesn't directly support mutation events, however, it notifies
  71. * the document when mutations are performed so that the document class do so.
  72. *
  73. * @version $Id: AttributeMap.java,v 1.28 2004/02/16 23:09:35 mrglavas Exp $
  74. */
  75. public class AttributeMap extends NamedNodeMapImpl {
  76. /** Serialization version. */
  77. static final long serialVersionUID = 8872606282138665383L;
  78. //
  79. // Constructors
  80. //
  81. /** Constructs a named node map. */
  82. protected AttributeMap(ElementImpl ownerNode, NamedNodeMapImpl defaults) {
  83. super(ownerNode);
  84. if (defaults != null) {
  85. // initialize map with the defaults
  86. cloneContent(defaults);
  87. if (nodes != null) {
  88. hasDefaults(true);
  89. }
  90. }
  91. }
  92. /**
  93. * Adds an attribute using its nodeName attribute.
  94. * @see org.w3c.dom.NamedNodeMap#setNamedItem
  95. * @return If the new Node replaces an existing node the replaced Node is
  96. * returned, otherwise null is returned.
  97. * @param arg
  98. * An Attr node to store in this map.
  99. * @exception org.w3c.dom.DOMException The exception description.
  100. */
  101. public Node setNamedItem(Node arg)
  102. throws DOMException {
  103. if (isReadOnly()) {
  104. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
  105. throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
  106. }
  107. if(arg.getOwnerDocument() != ownerNode.ownerDocument()) {
  108. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
  109. throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
  110. }
  111. if (arg.getNodeType() != Node.ATTRIBUTE_NODE) {
  112. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null);
  113. throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, msg);
  114. }
  115. AttrImpl argn = (AttrImpl)arg;
  116. if (argn.isOwned()){
  117. if (argn.getOwnerElement() != ownerNode) {
  118. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INUSE_ATTRIBUTE_ERR", null);
  119. throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, msg);
  120. }
  121. // replacing an Attribute with itself does nothing
  122. return arg;
  123. }
  124. // set owner
  125. argn.ownerNode = ownerNode;
  126. argn.isOwned(true);
  127. int i = findNamePoint(arg.getNodeName(),0);
  128. AttrImpl previous = null;
  129. if (i >= 0) {
  130. previous = (AttrImpl) nodes.elementAt(i);
  131. nodes.setElementAt(arg,i);
  132. previous.ownerNode = ownerNode.ownerDocument();
  133. previous.isOwned(false);
  134. // make sure it won't be mistaken with defaults in case it's reused
  135. previous.isSpecified(true);
  136. } else {
  137. i = -1 - i; // Insert point (may be end of list)
  138. if (null == nodes) {
  139. nodes = new Vector(5, 10);
  140. }
  141. nodes.insertElementAt(arg, i);
  142. }
  143. // notify document
  144. ownerNode.ownerDocument().setAttrNode(argn, previous);
  145. // If the new attribute is not normalized,
  146. // the owning element is inherently not normalized.
  147. if (!argn.isNormalized()) {
  148. ownerNode.isNormalized(false);
  149. }
  150. return previous;
  151. } // setNamedItem(Node):Node
  152. /**
  153. * Adds an attribute using its namespaceURI and localName.
  154. * @see org.w3c.dom.NamedNodeMap#setNamedItem
  155. * @return If the new Node replaces an existing node the replaced Node is
  156. * returned, otherwise null is returned.
  157. * @param arg A node to store in a named node map.
  158. */
  159. public Node setNamedItemNS(Node arg)
  160. throws DOMException {
  161. if (isReadOnly()) {
  162. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
  163. throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
  164. }
  165. if(arg.getOwnerDocument() != ownerNode.ownerDocument()) {
  166. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
  167. throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
  168. }
  169. if (arg.getNodeType() != Node.ATTRIBUTE_NODE) {
  170. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null);
  171. throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, msg);
  172. }
  173. AttrImpl argn = (AttrImpl)arg;
  174. if (argn.isOwned()){
  175. if (argn.getOwnerElement() != ownerNode) {
  176. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INUSE_ATTRIBUTE_ERR", null);
  177. throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, msg);
  178. }
  179. // replacing an Attribute with itself does nothing
  180. return arg;
  181. }
  182. // set owner
  183. argn.ownerNode = ownerNode;
  184. argn.isOwned(true);
  185. int i = findNamePoint(argn.getNamespaceURI(), argn.getLocalName());
  186. AttrImpl previous = null;
  187. if (i >= 0) {
  188. previous = (AttrImpl) nodes.elementAt(i);
  189. nodes.setElementAt(arg,i);
  190. previous.ownerNode = ownerNode.ownerDocument();
  191. previous.isOwned(false);
  192. // make sure it won't be mistaken with defaults in case it's reused
  193. previous.isSpecified(true);
  194. } else {
  195. // If we can't find by namespaceURI, localName, then we find by
  196. // nodeName so we know where to insert.
  197. i = findNamePoint(arg.getNodeName(),0);
  198. if (i >=0) {
  199. previous = (AttrImpl) nodes.elementAt(i);
  200. nodes.insertElementAt(arg,i);
  201. } else {
  202. i = -1 - i; // Insert point (may be end of list)
  203. if (null == nodes) {
  204. nodes = new Vector(5, 10);
  205. }
  206. nodes.insertElementAt(arg, i);
  207. }
  208. }
  209. // changed(true);
  210. // notify document
  211. ownerNode.ownerDocument().setAttrNode(argn, previous);
  212. // If the new attribute is not normalized,
  213. // the owning element is inherently not normalized.
  214. if (!argn.isNormalized()) {
  215. ownerNode.isNormalized(false);
  216. }
  217. return previous;
  218. } // setNamedItemNS(Node):Node
  219. /**
  220. * Removes an attribute specified by name.
  221. * @param name
  222. * The name of a node to remove. If the
  223. * removed attribute is known to have a default value, an
  224. * attribute immediately appears containing the default value
  225. * as well as the corresponding namespace URI, local name,
  226. * and prefix when applicable.
  227. * @return The node removed from the map if a node with such a name exists.
  228. * @throws NOT_FOUND_ERR: Raised if there is no node named
  229. * name in the map.
  230. */
  231. /***/
  232. public Node removeNamedItem(String name)
  233. throws DOMException {
  234. return internalRemoveNamedItem(name, true);
  235. }
  236. /**
  237. * Same as removeNamedItem except that it simply returns null if the
  238. * specified name is not found.
  239. */
  240. Node safeRemoveNamedItem(String name) {
  241. return internalRemoveNamedItem(name, false);
  242. }
  243. /**
  244. * NON-DOM: Remove the node object
  245. *
  246. * NOTE: Specifically removes THIS NODE -- not the node with this
  247. * name, nor the node with these contents. If node does not belong to
  248. * this named node map, we throw a DOMException.
  249. *
  250. * @param item The node to remove
  251. * @param addDefault true -- magically add default attribute
  252. * @return Removed node
  253. * @exception DOMException
  254. */
  255. protected Node removeItem(Node item, boolean addDefault)
  256. throws DOMException {
  257. int index = -1;
  258. if (nodes != null) {
  259. for (int i = 0; i < nodes.size(); i++) {
  260. if (nodes.elementAt(i) == item) {
  261. index = i;
  262. break;
  263. }
  264. }
  265. }
  266. if (index < 0) {
  267. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
  268. throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
  269. }
  270. return remove((AttrImpl)item, index, addDefault);
  271. }
  272. /**
  273. * Internal removeNamedItem method allowing to specify whether an exception
  274. * must be thrown if the specified name is not found.
  275. */
  276. final protected Node internalRemoveNamedItem(String name, boolean raiseEx){
  277. if (isReadOnly()) {
  278. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
  279. throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
  280. }
  281. int i = findNamePoint(name,0);
  282. if (i < 0) {
  283. if (raiseEx) {
  284. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
  285. throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
  286. } else {
  287. return null;
  288. }
  289. }
  290. return remove((AttrImpl)nodes.elementAt(i), i, true);
  291. } // internalRemoveNamedItem(String,boolean):Node
  292. private final Node remove(AttrImpl attr, int index,
  293. boolean addDefault) {
  294. CoreDocumentImpl ownerDocument = ownerNode.ownerDocument();
  295. String name = attr.getNodeName();
  296. if (attr.isIdAttribute()) {
  297. ownerDocument.removeIdentifier(attr.getValue());
  298. }
  299. if (hasDefaults() && addDefault) {
  300. // If there's a default, add it instead
  301. NamedNodeMapImpl defaults =
  302. ((ElementImpl) ownerNode).getDefaultAttributes();
  303. Node d;
  304. if (defaults != null &&
  305. (d = defaults.getNamedItem(name)) != null &&
  306. findNamePoint(name, index+1) < 0) {
  307. NodeImpl clone = (NodeImpl)d.cloneNode(true);
  308. if (d.getLocalName() !=null){
  309. // we must rely on the name to find a default attribute
  310. // ("test:attr"), but while copying it from the DOCTYPE
  311. // we should not loose namespace URI that was assigned
  312. // to the attribute in the instance document.
  313. ((AttrNSImpl)clone).namespaceURI = attr.getNamespaceURI();
  314. }
  315. clone.ownerNode = ownerNode;
  316. clone.isOwned(true);
  317. clone.isSpecified(false);
  318. nodes.setElementAt(clone, index);
  319. if (attr.isIdAttribute()) {
  320. ownerDocument.putIdentifier(clone.getNodeValue(),
  321. (ElementImpl)ownerNode);
  322. }
  323. } else {
  324. nodes.removeElementAt(index);
  325. }
  326. } else {
  327. nodes.removeElementAt(index);
  328. }
  329. // changed(true);
  330. // remove reference to owner
  331. attr.ownerNode = ownerDocument;
  332. attr.isOwned(false);
  333. // make sure it won't be mistaken with defaults in case it's
  334. // reused
  335. attr.isSpecified(true);
  336. attr.isIdAttribute(false);
  337. // notify document
  338. ownerDocument.removedAttrNode(attr, ownerNode, name);
  339. return attr;
  340. }
  341. /**
  342. * Introduced in DOM Level 2. <p>
  343. * Removes an attribute specified by local name and namespace URI.
  344. * @param namespaceURI
  345. * The namespace URI of the node to remove.
  346. * When it is null or an empty string, this
  347. * method behaves like removeNamedItem.
  348. * @param The local name of the node to remove. If the
  349. * removed attribute is known to have a default
  350. * value, an attribute immediately appears
  351. * containing the default value.
  352. * @return Node The node removed from the map if a node with such
  353. * a local name and namespace URI exists.
  354. * @throws NOT_FOUND_ERR: Raised if there is no node named
  355. * name in the map.
  356. */
  357. public Node removeNamedItemNS(String namespaceURI, String name)
  358. throws DOMException {
  359. return internalRemoveNamedItemNS(namespaceURI, name, true);
  360. }
  361. /**
  362. * Same as removeNamedItem except that it simply returns null if the
  363. * specified local name and namespace URI is not found.
  364. */
  365. Node safeRemoveNamedItemNS(String namespaceURI, String name) {
  366. return internalRemoveNamedItemNS(namespaceURI, name, false);
  367. }
  368. /**
  369. * Internal removeNamedItemNS method allowing to specify whether an
  370. * exception must be thrown if the specified local name and namespace URI
  371. * is not found.
  372. */
  373. final protected Node internalRemoveNamedItemNS(String namespaceURI,
  374. String name,
  375. boolean raiseEx) {
  376. if (isReadOnly()) {
  377. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
  378. throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
  379. }
  380. int i = findNamePoint(namespaceURI, name);
  381. if (i < 0) {
  382. if (raiseEx) {
  383. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
  384. throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
  385. } else {
  386. return null;
  387. }
  388. }
  389. AttrImpl n = (AttrImpl)nodes.elementAt(i);
  390. CoreDocumentImpl ownerDocument = ownerNode.ownerDocument();
  391. if (n.isIdAttribute()) {
  392. ownerDocument.removeIdentifier(n.getValue());
  393. }
  394. // If there's a default, add it instead
  395. String nodeName = n.getNodeName();
  396. if (hasDefaults()) {
  397. NamedNodeMapImpl defaults = ((ElementImpl) ownerNode).getDefaultAttributes();
  398. Node d;
  399. if (defaults != null
  400. && (d = defaults.getNamedItem(nodeName)) != null)
  401. {
  402. int j = findNamePoint(nodeName,0);
  403. if (j>=0 && findNamePoint(nodeName, j+1) < 0) {
  404. NodeImpl clone = (NodeImpl)d.cloneNode(true);
  405. clone.ownerNode = ownerNode;
  406. if (d.getLocalName() != null) {
  407. // we must rely on the name to find a default attribute
  408. // ("test:attr"), but while copying it from the DOCTYPE
  409. // we should not loose namespace URI that was assigned
  410. // to the attribute in the instance document.
  411. ((AttrNSImpl)clone).namespaceURI = namespaceURI;
  412. }
  413. clone.isOwned(true);
  414. clone.isSpecified(false);
  415. nodes.setElementAt(clone, i);
  416. if (clone.isIdAttribute()) {
  417. ownerDocument.putIdentifier(clone.getNodeValue(),
  418. (ElementImpl)ownerNode);
  419. }
  420. } else {
  421. nodes.removeElementAt(i);
  422. }
  423. } else {
  424. nodes.removeElementAt(i);
  425. }
  426. } else {
  427. nodes.removeElementAt(i);
  428. }
  429. // changed(true);
  430. // remove reference to owner
  431. n.ownerNode = ownerDocument;
  432. n.isOwned(false);
  433. // make sure it won't be mistaken with defaults in case it's
  434. // reused
  435. n.isSpecified(true);
  436. // update id table if needed
  437. n.isIdAttribute(false);
  438. // notify document
  439. ownerDocument.removedAttrNode(n, ownerNode, name);
  440. return n;
  441. } // internalRemoveNamedItemNS(String,String,boolean):Node
  442. //
  443. // Public methods
  444. //
  445. /**
  446. * Cloning a NamedNodeMap is a DEEP OPERATION; it always clones
  447. * all the nodes contained in the map.
  448. */
  449. public NamedNodeMapImpl cloneMap(NodeImpl ownerNode) {
  450. AttributeMap newmap =
  451. new AttributeMap((ElementImpl) ownerNode, null);
  452. newmap.hasDefaults(hasDefaults());
  453. newmap.cloneContent(this);
  454. return newmap;
  455. } // cloneMap():AttributeMap
  456. /**
  457. * Override parent's method to set the ownerNode correctly
  458. */
  459. protected void cloneContent(NamedNodeMapImpl srcmap) {
  460. Vector srcnodes = srcmap.nodes;
  461. if (srcnodes != null) {
  462. int size = srcnodes.size();
  463. if (size != 0) {
  464. if (nodes == null) {
  465. nodes = new Vector(size);
  466. }
  467. nodes.setSize(size);
  468. for (int i = 0; i < size; ++i) {
  469. NodeImpl n = (NodeImpl) srcnodes.elementAt(i);
  470. NodeImpl clone = (NodeImpl) n.cloneNode(true);
  471. clone.isSpecified(n.isSpecified());
  472. nodes.setElementAt(clone, i);
  473. clone.ownerNode = ownerNode;
  474. clone.isOwned(true);
  475. }
  476. }
  477. }
  478. } // cloneContent():AttributeMap
  479. /**
  480. * Move specified attributes from the given map to this one
  481. */
  482. void moveSpecifiedAttributes(AttributeMap srcmap) {
  483. int nsize = (srcmap.nodes != null) ? srcmap.nodes.size() : 0;
  484. for (int i = nsize - 1; i >= 0; i--) {
  485. AttrImpl attr = (AttrImpl) srcmap.nodes.elementAt(i);
  486. if (attr.isSpecified()) {
  487. srcmap.remove(attr, i, false);
  488. if (attr.getLocalName() != null) {
  489. setNamedItem(attr);
  490. }
  491. else {
  492. setNamedItemNS(attr);
  493. }
  494. }
  495. }
  496. } // moveSpecifiedAttributes(AttributeMap):void
  497. /**
  498. * Get this AttributeMap in sync with the given "defaults" map.
  499. * @param defaults The default attributes map to sync with.
  500. */
  501. protected void reconcileDefaults(NamedNodeMapImpl defaults) {
  502. // remove any existing default
  503. int nsize = (nodes != null) ? nodes.size() : 0;
  504. for (int i = nsize - 1; i >= 0; i--) {
  505. AttrImpl attr = (AttrImpl) nodes.elementAt(i);
  506. if (!attr.isSpecified()) {
  507. remove(attr, i, false);
  508. }
  509. }
  510. // add the new defaults
  511. if (defaults == null) {
  512. return;
  513. }
  514. if (nodes == null || nodes.size() == 0) {
  515. cloneContent(defaults);
  516. }
  517. else {
  518. int dsize = defaults.nodes.size();
  519. for (int n = 0; n < dsize; n++) {
  520. AttrImpl d = (AttrImpl) defaults.nodes.elementAt(n);
  521. int i = findNamePoint(d.getNodeName(), 0);
  522. if (i < 0) {
  523. i = -1 - i;
  524. NodeImpl clone = (NodeImpl) d.cloneNode(true);
  525. clone.ownerNode = ownerNode;
  526. clone.isOwned(true);
  527. clone.isSpecified(false);
  528. nodes.insertElementAt(clone, i);
  529. }
  530. }
  531. }
  532. } // reconcileDefaults()
  533. } // class AttributeMap