001: /*
002: * The Apache Software License, Version 1.1
003: *
004: *
005: * Copyright (c) 2000-2001 The Apache Software Foundation. All rights
006: * reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * 1. Redistributions of source code must retain the above copyright
013: * notice, this list of conditions and the following disclaimer.
014: *
015: * 2. Redistributions in binary form must reproduce the above copyright
016: * notice, this list of conditions and the following disclaimer in
017: * the documentation and/or other materials provided with the
018: * distribution.
019: *
020: * 3. The end-user documentation included with the redistribution,
021: * if any, must include the following acknowledgment:
022: * "This product includes software developed by the
023: * Apache Software Foundation (http://www.apache.org/)."
024: * Alternately, this acknowledgment may appear in the software itself,
025: * if and wherever such third-party acknowledgments normally appear.
026: *
027: * 4. The names "Xerces" and "Apache Software Foundation" must
028: * not be used to endorse or promote products derived from this
029: * software without prior written permission. For written
030: * permission, please contact apache@apache.org.
031: *
032: * 5. Products derived from this software may not be called "Apache",
033: * nor may "Apache" appear in their name, without prior written
034: * permission of the Apache Software Foundation.
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
040: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: * ====================================================================
049: *
050: * This software consists of voluntary contributions made by many
051: * individuals on behalf of the Apache Software Foundation and was
052: * originally based on software copyright (c) 1999, International
053: * Business Machines, Inc., http://www.apache.org. For more
054: * information on the Apache Software Foundation, please see
055: * <http://www.apache.org/>.
056: */
057:
058: package org.apache.xerces.dom;
059:
060: import java.util.Vector;
061:
062: import org.w3c.dom.DOMException;
063: import org.w3c.dom.Node;
064:
065: /**
066: * AttributeMap inherits from NamedNodeMapImpl and extends it to deal with the
067: * specifics of storing attributes. These are:
068: * <ul>
069: * <li>managing ownership of attribute nodes
070: * <li>managing default attributes
071: * <li>firing mutation events
072: * </ul>
073: * <p>
074: * This class doesn't directly support mutation events, however, it notifies
075: * the document when mutations are performed so that the document class do so.
076: *
077: */
078: public class AttributeMap extends NamedNodeMapImpl {
079:
080: //
081: // Constructors
082: //
083:
084: /** Constructs a named node map. */
085: protected AttributeMap(ElementImpl ownerNode,
086: NamedNodeMapImpl defaults) {
087: super (ownerNode);
088: if (defaults != null) {
089: // initialize map with the defaults
090: cloneContent(defaults);
091: if (nodes != null) {
092: hasDefaults(true);
093: }
094: }
095: }
096:
097: /**
098: * Adds an attribute using its nodeName attribute.
099: * @see org.w3c.dom.NamedNodeMap#setNamedItem
100: * @return If the new Node replaces an existing node the replaced Node is
101: * returned, otherwise null is returned.
102: * @param arg
103: * An Attr node to store in this map.
104: * @exception org.w3c.dom.DOMException The exception description.
105: */
106: public Node setNamedItem(Node arg) throws DOMException {
107:
108: if (isReadOnly()) {
109: throw new DOMException(
110: DOMException.NO_MODIFICATION_ALLOWED_ERR,
111: "DOM001 Modification not allowed");
112: }
113: if (arg.getOwnerDocument() != ownerNode.ownerDocument()) {
114: throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
115: "DOM005 Wrong document");
116: }
117: if (arg.getNodeType() != arg.ATTRIBUTE_NODE) {
118: throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
119: "DOM006 Hierarchy request error");
120: }
121:
122: AttrImpl argn = (AttrImpl) arg;
123:
124: if (argn.isOwned()) {
125: throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR,
126: "DOM009 Attribute already in use");
127: }
128:
129: // set owner
130: argn.ownerNode = ownerNode;
131: argn.isOwned(true);
132:
133: int i = findNamePoint(arg.getNodeName(), 0);
134: AttrImpl previous = null;
135: if (i >= 0) {
136: previous = (AttrImpl) nodes.elementAt(i);
137: nodes.setElementAt(arg, i);
138: previous.ownerNode = ownerNode.ownerDocument();
139: previous.isOwned(false);
140: // make sure it won't be mistaken with defaults in case it's reused
141: previous.isSpecified(true);
142: } else {
143: i = -1 - i; // Insert point (may be end of list)
144: if (null == nodes) {
145: nodes = new Vector(5, 10);
146: }
147: nodes.insertElementAt(arg, i);
148: }
149:
150: // notify document
151: ownerNode.ownerDocument().setAttrNode(argn, previous);
152:
153: // If the new attribute is not normalized,
154: // the owning element is inherently not normalized.
155: if (!argn.isNormalized()) {
156: ownerNode.isNormalized(false);
157: }
158: return previous;
159:
160: } // setNamedItem(Node):Node
161:
162: /**
163: * Adds an attribute using its namespaceURI and localName.
164: * @see org.w3c.dom.NamedNodeMap#setNamedItem
165: * @return If the new Node replaces an existing node the replaced Node is
166: * returned, otherwise null is returned.
167: * @param arg A node to store in a named node map.
168: */
169: public Node setNamedItemNS(Node arg) throws DOMException {
170:
171: if (isReadOnly()) {
172: throw new DOMException(
173: DOMException.NO_MODIFICATION_ALLOWED_ERR,
174: "DOM001 Modification not allowed");
175: }
176:
177: if (arg.getOwnerDocument() != ownerNode.ownerDocument()) {
178: throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
179: "DOM005 Wrong document");
180: }
181:
182: if (arg.getNodeType() != arg.ATTRIBUTE_NODE) {
183: throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
184: "DOM006 Hierarchy request error");
185: }
186: AttrImpl argn = (AttrImpl) arg;
187: if (argn.isOwned()) {
188: throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR,
189: "DOM009 Attribute already in use");
190: }
191:
192: // set owner
193: argn.ownerNode = ownerNode;
194: argn.isOwned(true);
195:
196: int i = findNamePoint(argn.getNamespaceURI(), argn
197: .getLocalName());
198: AttrImpl previous = null;
199: if (i >= 0) {
200: previous = (AttrImpl) nodes.elementAt(i);
201: nodes.setElementAt(arg, i);
202: previous.ownerNode = ownerNode.ownerDocument();
203: previous.isOwned(false);
204: // make sure it won't be mistaken with defaults in case it's reused
205: previous.isSpecified(true);
206: } else {
207: // If we can't find by namespaceURI, localName, then we find by
208: // nodeName so we know where to insert.
209: i = findNamePoint(arg.getNodeName(), 0);
210: if (i >= 0) {
211: previous = (AttrImpl) nodes.elementAt(i);
212: nodes.insertElementAt(arg, i);
213: } else {
214: i = -1 - i; // Insert point (may be end of list)
215: if (null == nodes) {
216: nodes = new Vector(5, 10);
217: }
218: nodes.insertElementAt(arg, i);
219: }
220: }
221: // changed(true);
222:
223: // notify document
224: ownerNode.ownerDocument().setAttrNode(argn, previous);
225:
226: // If the new attribute is not normalized,
227: // the owning element is inherently not normalized.
228: if (!argn.isNormalized()) {
229: ownerNode.isNormalized(false);
230: }
231: return previous;
232:
233: } // setNamedItemNS(Node):Node
234:
235: /**
236: * Removes an attribute specified by name.
237: * @param name
238: * The name of a node to remove. If the
239: * removed attribute is known to have a default value, an
240: * attribute immediately appears containing the default value
241: * as well as the corresponding namespace URI, local name,
242: * and prefix when applicable.
243: * @return The node removed from the map if a node with such a name exists.
244: * @throws NOT_FOUND_ERR: Raised if there is no node named
245: * name in the map.
246: */
247: /***/
248: public Node removeNamedItem(String name) throws DOMException {
249: return internalRemoveNamedItem(name, true);
250: }
251:
252: /**
253: * Same as removeNamedItem except that it simply returns null if the
254: * specified name is not found.
255: */
256: Node safeRemoveNamedItem(String name) {
257: return internalRemoveNamedItem(name, false);
258: }
259:
260: /**
261: * Internal removeNamedItem method allowing to specify whether an exception
262: * must be thrown if the specified name is not found.
263: */
264: final protected Node internalRemoveNamedItem(String name,
265: boolean raiseEx) {
266: if (isReadOnly()) {
267: throw new DOMException(
268: DOMException.NO_MODIFICATION_ALLOWED_ERR,
269: "DOM001 Modification not allowed");
270: }
271: int i = findNamePoint(name, 0);
272: if (i < 0) {
273: if (raiseEx) {
274: throw new DOMException(DOMException.NOT_FOUND_ERR,
275: "DOM008 Not found");
276: } else {
277: return null;
278: }
279: }
280:
281: AttrImpl n = (AttrImpl) nodes.elementAt(i);
282: CoreDocumentImpl ownerDocument = ownerNode.ownerDocument();
283:
284: // If there's a default, add it instead
285: if (hasDefaults()) {
286: NamedNodeMapImpl defaults = ((ElementImpl) ownerNode)
287: .getDefaultAttributes();
288: Node d;
289: if (defaults != null
290: && (d = defaults.getNamedItem(name)) != null
291: && findNamePoint(name, i + 1) < 0) {
292:
293: NodeImpl clone = (NodeImpl) d.cloneNode(true);
294: clone.ownerNode = ownerNode;
295: clone.isOwned(true);
296: clone.isSpecified(false);
297: nodes.setElementAt(clone, i);
298: } else {
299: nodes.removeElementAt(i);
300: }
301: } else {
302: nodes.removeElementAt(i);
303: }
304:
305: // changed(true);
306:
307: // remove reference to owner
308: n.ownerNode = ownerDocument;
309: n.isOwned(false);
310: // make sure it won't be mistaken with defaults in case it's reused
311: n.isSpecified(true);
312:
313: // notify document
314: ownerDocument.removedAttrNode(n, ownerNode, name);
315:
316: return n;
317:
318: } // internalRemoveNamedItem(String,boolean):Node
319:
320: /**
321: * Introduced in DOM Level 2. <p>
322: * Removes an attribute specified by local name and namespace URI.
323: * @param namespaceURI
324: * The namespace URI of the node to remove.
325: * When it is null or an empty string, this
326: * method behaves like removeNamedItem.
327: * @param The local name of the node to remove. If the
328: * removed attribute is known to have a default
329: * value, an attribute immediately appears
330: * containing the default value.
331: * @return Node The node removed from the map if a node with such
332: * a local name and namespace URI exists.
333: * @throws NOT_FOUND_ERR: Raised if there is no node named
334: * name in the map.
335: */
336: public Node removeNamedItemNS(String namespaceURI, String name)
337: throws DOMException {
338: return internalRemoveNamedItemNS(namespaceURI, name, true);
339: }
340:
341: /**
342: * Same as removeNamedItem except that it simply returns null if the
343: * specified local name and namespace URI is not found.
344: */
345: Node safeRemoveNamedItemNS(String namespaceURI, String name) {
346: return internalRemoveNamedItemNS(namespaceURI, name, false);
347: }
348:
349: /**
350: * Internal removeNamedItemNS method allowing to specify whether an
351: * exception must be thrown if the specified local name and namespace URI
352: * is not found.
353: */
354: final protected Node internalRemoveNamedItemNS(String namespaceURI,
355: String name, boolean raiseEx) {
356: if (isReadOnly()) {
357: throw new DOMException(
358: DOMException.NO_MODIFICATION_ALLOWED_ERR,
359: "DOM001 Modification not allowed");
360: }
361: int i = findNamePoint(namespaceURI, name);
362: if (i < 0) {
363: if (raiseEx) {
364: throw new DOMException(DOMException.NOT_FOUND_ERR,
365: "DOM008 Not found");
366: } else {
367: return null;
368: }
369: }
370:
371: AttrImpl n = (AttrImpl) nodes.elementAt(i);
372: CoreDocumentImpl ownerDocument = ownerNode.ownerDocument();
373:
374: // If there's a default, add it instead
375: String nodeName = n.getNodeName();
376: if (hasDefaults()) {
377: NamedNodeMapImpl defaults = ((ElementImpl) ownerNode)
378: .getDefaultAttributes();
379: Node d;
380: if (defaults != null
381: && (d = defaults.getNamedItem(nodeName)) != null) {
382: int j = findNamePoint(nodeName, 0);
383: if (j >= 0 && findNamePoint(nodeName, j + 1) < 0) {
384: NodeImpl clone = (NodeImpl) d.cloneNode(true);
385: clone.ownerNode = ownerNode;
386: // we must rely on the name to find a default attribute
387: // ("test:attr"), but while copying it from the DOCTYPE
388: // we should not loose namespace URI that was assigned
389: // to the attribute in the instance document.
390: if (clone instanceof AttrNSImpl) {
391: ((AttrNSImpl) clone).namespaceURI = namespaceURI;
392: }
393: clone.isOwned(true);
394: clone.isSpecified(false);
395: nodes.setElementAt(clone, i);
396: } else {
397: nodes.removeElementAt(i);
398: }
399: } else {
400: nodes.removeElementAt(i);
401: }
402: } else {
403: nodes.removeElementAt(i);
404: }
405:
406: // changed(true);
407:
408: // remove reference to owner
409: n.ownerNode = ownerDocument;
410: n.isOwned(false);
411: // make sure it won't be mistaken with defaults in case it's reused
412: n.isSpecified(true);
413:
414: // notify document
415: ownerDocument.removedAttrNode(n, ownerNode, name);
416:
417: return n;
418:
419: } // internalRemoveNamedItemNS(String,String,boolean):Node
420:
421: //
422: // Public methods
423: //
424:
425: /**
426: * Cloning a NamedNodeMap is a DEEP OPERATION; it always clones
427: * all the nodes contained in the map.
428: */
429:
430: public NamedNodeMapImpl cloneMap(NodeImpl ownerNode) {
431: AttributeMap newmap = new AttributeMap((ElementImpl) ownerNode,
432: null);
433: newmap.hasDefaults(hasDefaults());
434: newmap.cloneContent(this );
435: return newmap;
436: } // cloneMap():AttributeMap
437:
438: /**
439: * Override parent's method to set the ownerNode correctly
440: */
441: protected void cloneContent(NamedNodeMapImpl srcmap) {
442: if (srcmap.nodes != null) {
443: if (nodes == null) {
444: nodes = new Vector(srcmap.nodes.size());
445: } else {
446: nodes.setSize(srcmap.nodes.size());
447: }
448: for (int i = 0; i < srcmap.nodes.size(); ++i) {
449: NodeImpl n = (NodeImpl) srcmap.nodes.elementAt(i);
450: NodeImpl clone = (NodeImpl) n.cloneNode(true);
451: clone.isSpecified(n.isSpecified());
452: nodes.insertElementAt(clone, i);
453: clone.ownerNode = ownerNode;
454: clone.isOwned(true);
455: }
456: }
457: } // cloneContent():AttributeMap
458:
459: /**
460: * Get this AttributeMap in sync with the given "defaults" map.
461: * @param defaults The default attributes map to sync with.
462: */
463: protected void reconcileDefaults(NamedNodeMapImpl defaults) {
464:
465: // remove any existing default
466: int nsize = (nodes != null) ? nodes.size() : 0;
467: for (int i = nsize - 1; i >= 0; i--) {
468: AttrImpl attr = (AttrImpl) nodes.elementAt(i);
469: if (!attr.isSpecified()) {
470: // remove owning element
471: attr.ownerNode = ownerNode.ownerDocument();
472: attr.isOwned(false);
473: // make sure it won't be mistaken in case it's reused
474: attr.isSpecified(true);
475: nodes.removeElementAt(i);
476: }
477: }
478: // add the new defaults
479: if (defaults == null) {
480: return;
481: }
482: if (nodes == null || nodes.size() == 0) {
483: cloneContent(defaults);
484: } else {
485: int dsize = defaults.nodes.size();
486: for (int n = 0; n < dsize; n++) {
487: AttrImpl d = (AttrImpl) defaults.nodes.elementAt(n);
488: int i = findNamePoint(d.getNodeName(), 0);
489: if (i < 0) {
490: NodeImpl clone = (NodeImpl) d.cloneNode(true);
491: clone.ownerNode = ownerNode;
492: clone.isOwned(true);
493: clone.isSpecified(false);
494: nodes.setElementAt(clone, i);
495: }
496: }
497: }
498:
499: } // reconcileDefaults()
500:
501: } // class AttributeMap
|