/*
* $Id: AttributeSet.java,v 1.15 2001/07/13 00:00:30 edwingo Exp $
*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Crimson" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999, Sun Microsystems, Inc.,
* http://www.sun.com. For more information on the Apache Software
* Foundation, please see <http://www.apache.org/>.
*/
package org.apache.crimson.tree;
import java.io.CharArrayWriter;
import java.io.Writer;
import java.io.IOException;
import java.util.Vector;
import org.w3c.dom.*;
import org.xml.sax.Attributes;
import org.apache.crimson.parser.AttributesEx;
import org.apache.crimson.util.XmlNames;
/**
* Class representing a list of XML attributes.
*
* <P> This couples slightly with the Sun XML parser, in that it optionally
* uses an extended SAX API to see if an attribute was specified in the
* document or was instead defaulted by attribute processing.
*
* @author David Brownell
* @version $Revision: 1.15 $
*/
final
class AttributeSet implements NamedNodeMap, XmlWritable
{
private boolean readonly;
private Vector list;
private Element ownerElement;
private AttributeSet() {
// no-arg constructor
}
/* Constructs an attribute list, with associated name scope. */
// package private
AttributeSet(Element ownerElement) {
list = new Vector (5);
this.ownerElement = ownerElement;
}
/*
* Constructs a copy of an attribute list, for use in cloning.
* AttributeNode.ownerElement is set later when the attributes are
* associated with an Element.
*/
// package private
AttributeSet (AttributeSet original, boolean deep)
{
int size = original.getLength ();
list = new Vector (size);
for (int i = 0; i < size; i++) {
Node node = original.item (i);
if (!(node instanceof AttributeNode))
throw new IllegalArgumentException (((NodeBase)node).
getMessage ("A-003"));
AttributeNode attr = (AttributeNode)node;
node = attr.cloneAttributeNode(deep);
list.addElement (node);
}
}
/**
* Constructor used to implement Document.importNode(). Only
* "specified" Attr nodes are copied. Copy is always deep.
*/
AttributeSet(AttributeSet original) {
int size = original.getLength();
list = new Vector(size);
for (int i = 0; i < size; i++) {
Node node = original.item(i);
if (!(node instanceof AttributeNode)) {
throw new IllegalArgumentException(((NodeBase)node).
getMessage ("A-003"));
}
AttributeNode attr = (AttributeNode) node;
// Copy only specified attributes
if (attr.getSpecified()) {
node = attr.cloneAttributeNode(true);
list.addElement(node);
}
}
list.trimToSize();
}
/**
* Create a DOM NamedNodeMap consisting of DOM Level 2 Attr nodes from
* a SAX2 Attributes object
*/
static AttributeSet createAttributeSet2(Attributes source)
throws DOMException
{
AttributeSet retval = new AttributeSet();
int len = source.getLength();
AttributesEx ex = null;
retval.list = new Vector(len);
if (source instanceof AttributesEx) {
ex = (AttributesEx) source;
}
for (int i = 0; i < len; i++) {
// Process the namespaceURI according to DOM Level 2 spec
String uri;
String qName = source.getQName(i);
if ("xmlns".equals(qName)
|| "xmlns".equals(XmlNames.getPrefix(qName))) {
// Associate the right namespaceURI with "xmlns" attributes
uri = XmlNames.SPEC_XMLNS_URI;
} else {
uri = source.getURI(i);
// Translate "" of SAX2 to null. See DOM2 spec under Node
// namespaceURI
if ("".equals(uri)) {
uri = null;
}
}
AttributeNode attrNode =
new AttributeNode(uri, qName,
source.getValue(i),
ex == null // remember if it was specified
? true
: ex.isSpecified(i),
ex == null // remember any default value
? null
: ex.getDefault(i));
retval.list.addElement(attrNode);
}
return retval;
}
/**
* Create a DOM NamedNodeMap consisting of DOM Level 1 Attr nodes from
* a SAX2 Attributes object
*/
static AttributeSet createAttributeSet1(Attributes source)
throws DOMException
{
AttributeSet retval = new AttributeSet();
int len = source.getLength();
AttributesEx ex = null;
retval.list = new Vector(len);
if (source instanceof AttributesEx) {
ex = (AttributesEx) source;
}
for (int i = 0; i < len; i++) {
AttributeNode1 attrNode1 = new AttributeNode1(
source.getQName(i),
source.getValue(i),
ex == null // remember if it was specified
? true
: ex.isSpecified(i),
ex == null // remember any default value
? null
: ex.getDefault(i));
retval.list.addElement(attrNode1);
}
return retval;
}
// package private
void trimToSize () { list.trimToSize (); }
// package private
public void setReadonly ()
{
readonly = true;
for (int i = 0; i < list.size (); i++)
((AttributeNode)list.elementAt (i)).setReadonly (true);
}
public boolean isReadonly () {
if (readonly)
return true;
for (int i = 0; i < list.size (); i++) {
if (((AttributeNode)list.elementAt (i)).isReadonly ()) {
return true;
}
}
return false;
}
// package private
void setOwnerElement(Element e) {
if (e != null && ownerElement != null) {
throw new IllegalStateException(((NodeBase)e).getMessage("A-004"));
}
ownerElement = e;
// need to bind the attributes to this element
int length = list.size();
for (int i = 0; i < length; i++) {
AttributeNode node;
node = (AttributeNode)list.elementAt(i);
node.setOwnerElement(null);
node.setOwnerElement(e);
}
}
// package private
String getValue (String name)
{
Attr attr = (Attr) getNamedItem (name);
if (attr == null)
return "";
else
return attr.getValue ();
}
public Node getNamedItem (String name)
{
int length = list.size ();
Node value;
for (int i = 0; i < length; i++) {
value = item (i);
if (value.getNodeName ().equals (name))
return value;
}
return null;
}
/**
* <b>DOM2:</b>
*/
public Node getNamedItemNS(String namespaceURI, String localName) {
// DOM L2 spec specifies that Attr.localName is null for L1 created
// Attr and Element nodes, therefore this method cannot be used to
// lookup such a node.
if (localName == null) {
return null;
}
for (int i = 0; i < list.size(); i++) {
Node value = item(i);
String iLocalName = value.getLocalName();
if (localName.equals(iLocalName)) {
String iNamespaceURI = value.getNamespaceURI();
if (namespaceURI == iNamespaceURI ||
(namespaceURI != null
&& namespaceURI.equals(iNamespaceURI))) {
return value;
}
}
}
return null;
}
public int getLength ()
{
return list.size ();
}
public Node item (int index)
{
if (index < 0 || index >= list.size ())
return null;
return (Node) list.elementAt (index);
}
public Node removeNamedItem(String name)
throws DOMException
{
if (readonly) {
throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
}
for (int i = 0; i < list.size(); i++) {
Node value = (Node)list.elementAt(i);
if (value.getNodeName().equals(name)) {
// Found a match
list.removeElementAt(i);
AttributeNode attr = (AttributeNode)value;
// Replace with Attr node of default value if it has one
String defaultValue = attr.getDefaultValue();
if (defaultValue != null) {
AttributeNode newAttr = attr.cloneAttributeNode(true);
newAttr.setOwnerElement(attr.getOwnerElement());
newAttr.setValue(defaultValue);
newAttr.setSpecified(false);
list.addElement(newAttr);
}
// Set the ownerElement of attr to null since we're
// removing it
attr.setOwnerElement(null);
return attr;
}
}
throw new DomEx(DomEx.NOT_FOUND_ERR);
}
/**
* <b>DOM2:</b>
*/
public Node removeNamedItemNS(String namespaceURI, String localName)
throws DOMException
{
if (readonly) {
throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
}
// See comments for getNamedItemNS() for why localName cannot be null
if (localName == null) {
throw new DomEx(DomEx.NOT_FOUND_ERR);
}
for (int i = 0; i < list.size(); i++) {
Node value = (Node)list.elementAt(i);
String iLocalName = value.getLocalName();
if (localName.equals(iLocalName)) {
String iNamespaceURI = value.getNamespaceURI();
if (namespaceURI == iNamespaceURI ||
(namespaceURI != null
&& namespaceURI.equals(iNamespaceURI))) {
// Found a match
list.removeElementAt(i);
AttributeNode attr = (AttributeNode)value;
// Replace with Attr node of default value if it has one
String defaultValue = attr.getDefaultValue();
if (defaultValue != null) {
AttributeNode newAttr = attr.cloneAttributeNode(true);
newAttr.setOwnerElement(attr.getOwnerElement());
newAttr.setValue(defaultValue);
newAttr.setSpecified(false);
list.addElement(newAttr);
}
// Set the ownerElement of attr to null since we're
// removing it
attr.setOwnerElement(null);
return attr;
}
}
}
throw new DomEx(DomEx.NOT_FOUND_ERR);
}
/**
* Note: this method both checks and sets the "ownerElement" of the
* "value" parameter. So if "ownerElement" is already set, an
* incorrect error results. Callers should avoid setting
* "ownerElement".
*/
public Node setNamedItem(Node value)
throws DOMException
{
if (readonly) {
throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
}
if (!(value instanceof AttributeNode) ||
value.getOwnerDocument() != ownerElement.getOwnerDocument()) {
throw new DomEx(DomEx.WRONG_DOCUMENT_ERR);
}
AttributeNode att = (AttributeNode)value;
if (att.getOwnerElement() != null) {
throw new DomEx(DomEx.INUSE_ATTRIBUTE_ERR);
}
int length = list.size();
AttributeNode oldAtt;
for (int i = 0; i < length; i++) {
oldAtt = (AttributeNode)item(i);
if (oldAtt.getNodeName().equals(value.getNodeName())) {
if (oldAtt.isReadonly()) {
throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
}
att.setOwnerElement(ownerElement);
list.setElementAt(att, i);
oldAtt.setOwnerElement(null);
return oldAtt;
}
}
att.setOwnerElement(ownerElement);
list.addElement(value);
return null;
}
/**
* <b>DOM2:</b>
* Spec technically allows other types of nodes also, but this code
* assumes Attr nodes only
*/
public Node setNamedItemNS(Node arg) throws DOMException {
if (readonly) {
throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
}
if (!(arg instanceof AttributeNode) ||
arg.getOwnerDocument() != ownerElement.getOwnerDocument()) {
throw new DomEx(DomEx.WRONG_DOCUMENT_ERR);
}
AttributeNode attr = (AttributeNode) arg;
if (attr.getOwnerElement() != null) {
throw new DomEx(DomEx.INUSE_ATTRIBUTE_ERR);
}
// Both localName and namespaceURI can be null for Attr nodes
// created by DOM Level 1 methods
String localName = attr.getLocalName();
String namespaceURI = attr.getNamespaceURI();
int length = list.size();
for (int i = 0; i < length; i++) {
AttributeNode oldNode = (AttributeNode) item(i);
String iLocalName = oldNode.getLocalName();
String iNamespaceURI = oldNode.getNamespaceURI();
if ((localName == iLocalName ||
(localName != null && localName.equals(iLocalName)))
&& (namespaceURI == iNamespaceURI ||
(namespaceURI != null
&& namespaceURI.equals(iNamespaceURI)))) {
// Found a matching node so replace it
if (oldNode.isReadonly()) {
throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR);
}
attr.setOwnerElement(ownerElement);
list.setElementAt(attr, i);
oldNode.setOwnerElement(null);
return oldNode;
}
}
// Append instead of replace
attr.setOwnerElement(ownerElement);
list.addElement(attr);
return null;
}
/**
* Writes out the attribute list. Attributes known to have been
* derived from the DTD are not (at this time) written out. Part
* of writing standalone XML is first ensuring that all attributes
* are flagged as being specified in the "printed" form (or else
* are defaulted only in the internal DTD subset).
*/
public void writeXml (XmlWriteContext context) throws IOException
{
Writer out = context.getWriter ();
int length = list.size ();
AttributeNode tmp;
for (int i = 0; i < length; i++) {
tmp = (AttributeNode) list.elementAt (i);
if (tmp.getSpecified ()) {
out.write (' ');
tmp.writeXml (context);
}
}
}
/**
* Does nothing; this type of node has no children.
*/
public void writeChildrenXml (XmlWriteContext context) throws IOException
{
}
public String toString ()
{
try {
CharArrayWriter w = new CharArrayWriter ();
XmlWriteContext x = new XmlWriteContext (w);
writeXml (x);
return w.toString ();
} catch (IOException e) {
return super.toString ();
}
}
}