net.sf.saxon.tree.util.AttributeCollectionImpl Maven / Gradle / Ivy
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2015 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon.tree.util;
import net.sf.saxon.Configuration;
import net.sf.saxon.event.ReceiverOptions;
import net.sf.saxon.expr.parser.ExplicitLocation;
import net.sf.saxon.expr.parser.Location;
import net.sf.saxon.om.*;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.MissingComponentException;
import net.sf.saxon.type.SimpleType;
import org.xml.sax.Attributes;
import java.util.Arrays;
/**
* AttributeCollectionImpl is an implementation of both the SAX2 interface Attributes
* and the Saxon equivalent AttributeCollection.
*
* As well as providing the information required by the SAX2 interface, an
* AttributeCollection can hold type information (as needed to support the JAXP 1.3
* {@link javax.xml.validation.ValidatorHandler} interface), and location information
* for debugging. The location information is used in the case of attributes on a result
* tree to identify the location in the query or stylesheet from which they were
* generated.
*/
public class AttributeCollectionImpl implements Attributes, AttributeCollection {
private Configuration config;
// Following fields can be null ONLY if used==0. We avoid allocating the arrays for the common
// case of an empty attribute collection.
private NodeName[] names = null;
private String[] values = null;
private Location[] locations = null;
private int[] props = null;
private int used = 0;
// the types array can be null even if used>0; this indicates that all attributes are untyped
private SimpleType[] types = null;
// Empty attribute collection. The caller is trusted not to try and modify it.
/*@NotNull*/ public static AttributeCollectionImpl EMPTY_ATTRIBUTE_COLLECTION =
new AttributeCollectionImpl((Configuration) null);
/**
* Create an empty attribute list.
*
* @param config the Saxon Configuration
*/
public AttributeCollectionImpl(Configuration config) {
this.config = config;
used = 0;
}
/**
* Create an attribute list as a copy of an existing attribute list
*
* @param atts the existing attribute list to be copied
* @return the copied attribute list. Note that if the original attribute list
* is empty, the method returns the singleton object {@link #EMPTY_ATTRIBUTE_COLLECTION};
* this case must therefore be handled specially if the returned attribute list is to
* be modified.
*/
/*@Nullable*/
public static AttributeCollectionImpl copy(/*@NotNull*/ AttributeCollectionImpl atts) {
if (atts.getLength() == 0) {
return EMPTY_ATTRIBUTE_COLLECTION;
}
AttributeCollectionImpl t = new AttributeCollectionImpl(atts.config);
t.used = atts.used;
t.names = new NodeName[atts.used];
t.values = new String[atts.used];
t.props = new int[atts.used];
t.locations = new Location[atts.used];
System.arraycopy(atts.names, 0, t.names, 0, atts.used);
System.arraycopy(atts.values, 0, t.values, 0, atts.used);
System.arraycopy(atts.props, 0, t.props, 0, atts.used);
System.arraycopy(atts.locations, 0, t.locations, 0, atts.used);
if (atts.types != null) {
t.types = new SimpleType[atts.used];
System.arraycopy(atts.types, 0, t.types, 0, atts.used);
}
return t;
}
/**
* Add an attribute to an attribute list. The parameters correspond
* to the parameters of the {@link net.sf.saxon.event.Receiver#attribute(NodeName, SimpleType, CharSequence, net.sf.saxon.expr.parser.Location, int)}
* method. There is no check that the name of the attribute is distinct from other attributes
* already in the collection: this check must be made by the caller.
*
* @param nodeName Object representing the attribute name.
* @param type The attribute type
* @param value The attribute value (must not be null)
* @param locationId Identifies the attribute location.
* @param properties Attribute properties
*/
public void addAttribute(NodeName nodeName, SimpleType type, String value, Location locationId, int properties) {
if (values == null) {
names = new NodeName[5];
values = new String[5];
props = new int[5];
locations = new Location[5];
if (!type.equals(BuiltInAtomicType.UNTYPED_ATOMIC)) {
types = new SimpleType[5];
}
used = 0;
}
if (values.length == used) {
int newsize = used == 0 ? 5 : used * 2;
NodeName[] n2 = new NodeName[newsize];
String[] v2 = new String[newsize];
int[] c2 = new int[newsize];
Location[] l2 = new Location[newsize];
System.arraycopy(names, 0, n2, 0, used);
System.arraycopy(values, 0, v2, 0, used);
System.arraycopy(this.props, 0, c2, 0, used);
System.arraycopy(this.locations, 0, l2, 0, used);
names = n2;
values = v2;
props = c2;
locations = l2;
if (types != null) {
SimpleType[] t2 = new SimpleType[newsize];
System.arraycopy(types, 0, t2, 0, used);
types = t2;
}
}
int n = used;
names[n] = nodeName;
props[n] = properties;
locations[n] = locationId.saveLocation();
setTypeAnnotation(n, type);
values[used++] = value;
}
/**
* Set (overwrite) an attribute in the attribute list. The parameters correspond
* to the parameters of the {@link net.sf.saxon.event.Receiver#attribute(NodeName, SimpleType, CharSequence, net.sf.saxon.expr.parser.Location, int)}
* method.
*
* @param index Identifies the entry to be replaced. Must be in range (nasty things happen if not)
* @param nodeName representing the attribute name.
* @param type The attribute type code
* @param value The attribute value (must not be null)
* @param locationId Identifies the attribtue location.
* @param properties Attribute properties
*/
public void setAttribute(int index, NodeName nodeName, SimpleType type, String value, Location locationId, int properties) {
names[index] = nodeName;
props[index] = properties;
locations[index] = locationId;
setTypeAnnotation(index, type);
values[index] = value;
}
/**
* Clear the attribute list. This removes the values but doesn't free the memory used.
* free the memory, use clear() then compact().
*/
public void clear() {
used = 0;
}
/**
* Compact the attribute list to avoid wasting memory
*/
public void compact() {
if (used == 0) {
props = null;
values = null;
} else if (values != null && values.length > used) {
NodeName[] n2 = new NodeName[used];
String[] v2 = new String[used];
int[] c2 = new int[used];
Location[] l2 = new Location[used];
System.arraycopy(names, 0, n2, 0, used);
System.arraycopy(values, 0, v2, 0, used);
System.arraycopy(props, 0, c2, 0, used);
System.arraycopy(locations, 0, l2, 0, used);
values = v2;
props = c2;
names = n2;
locations = l2;
if (types != null) {
SimpleType[] t2 = new SimpleType[used];
System.arraycopy(types, 0, t2, 0, used);
types = t2;
}
}
}
/**
* Return the number of attributes in the list.
*
* @return The number of attributes that have been created in this attribute collection. This is the number
* of slots used in the list, including any slots allocated to attributes that have since been deleted.
* Such slots are not reused, to preserve attribute identity.
*/
public int getLength() {
return (values == null ? 0 : used);
}
/**
* Get the namecode of an attribute (by position).
*
* @param index The position of the attribute in the list.
* @return The display name of the attribute as a string, or -1 if there
* is no attribute at that position.
*/
public int getNameCode(int index) {
if (names == null) {
return -1;
}
if (index < 0 || index >= used || names[index]==null) {
return -1;
}
return names[index].allocateNameCode(config.getNamePool());
}
/**
* Get the node name of an attribute (by position).
*
* @param index The position of the attribute in the list.
* @return The node name, as a NodeName object, or null if there is no name at this index
*/
/*@Nullable*/
public NodeName getNodeName(int index) {
if (names == null) {
return null;
}
if (index < 0 || index >= used) {
return null;
}
return names[index];
}
/**
* Get the type of an attribute (by position).
*
* @param index The position of the attribute in the list.
* @return The type annotation
*/
public SimpleType getTypeAnnotation(int index) {
if (types == null) {
return BuiltInAtomicType.UNTYPED_ATOMIC;
}
if (index < 0 || index >= used) {
return BuiltInAtomicType.UNTYPED_ATOMIC;
}
return types[index];
}
/**
* Get the location of an attribute (by position)
*
* @param index The position of the attribute in the list.
* @return The location of the attribute. This can be used to obtain the
* actual system identifier and line number of the relevant location
*/
public Location getLocation(int index) {
if (locations == null) {
return ExplicitLocation.UNKNOWN_LOCATION;
}
if (index < 0 || index >= used) {
return ExplicitLocation.UNKNOWN_LOCATION;
}
return locations[index];
}
/**
* Get the systemId part of the location of an attribute, at a given index.
*
* Attribute location information is not available from a SAX parser, so this method
* is not useful for getting the location of an attribute in a source document. However,
* in a Saxon result document, the location information represents the location in the
* stylesheet of the instruction used to generate this attribute, which is useful for
* debugging.
*
* @param index the required attribute
* @return the systemId of the location of the attribute
*/
public String getSystemId(int index) {
return getLocation(index).getSystemId();
}
/**
* Get the line number part of the location of an attribute, at a given index.
*
* Attribute location information is not available from a SAX parser, so this method
* is not useful for getting the location of an attribute in a source document. However,
* in a Saxon result document, the location information represents the location in the
* stylesheet of the instruction used to generate this attribute, which is useful for
* debugging.
*
* @param index the required attribute
* @return the line number of the location of the attribute
*/
public int getLineNumber(int index) {
return getLocation(index).getLineNumber();
}
/**
* Get the properties of an attribute (by position)
*
* @param index The position of the attribute in the list.
* @return The properties of the attribute. This is a set
* of bit-settings defined in class {@link net.sf.saxon.event.ReceiverOptions}. The
* most interesting of these is {{@link net.sf.saxon.event.ReceiverOptions#DEFAULTED_ATTRIBUTE},
* which indicates an attribute that was added to an element as a result of schema validation.
*/
public int getProperties(int index) {
if (props == null) {
return -1;
}
if (index < 0 || index >= used) {
return -1;
}
return props[index];
}
/**
* Get the prefix of the name of an attribute (by position).
*
* @param index The position of the attribute in the list.
* @return The prefix of the attribute name as a string, or null if there
* is no attribute at that position. Returns "" for an attribute that
* has no prefix.
*/
/*@Nullable*/
public String getPrefix(int index) {
if (names == null) {
return null;
}
if (index < 0 || index >= used) {
return null;
}
return names[index].getPrefix();
}
/**
* Get the lexical QName of an attribute (by position).
*
* @param index The position of the attribute in the list.
* @return The lexical QName of the attribute as a string, or null if there
* is no attribute at that position.
*/
/*@Nullable*/
public String getQName(int index) {
if (names == null) {
return null;
}
if (index < 0 || index >= used) {
return null;
}
return names[index].getDisplayName();
}
/**
* Get the local name of an attribute (by position).
*
* @param index The position of the attribute in the list.
* @return The local name of the attribute as a string, or null if there
* is no attribute at that position.
*/
/*@Nullable*/
public String getLocalName(int index) {
if (names == null) {
return null;
}
if (index < 0 || index >= used) {
return null;
}
return names[index].getLocalPart();
}
/**
* Get the namespace URI of an attribute (by position).
*
* @param index The position of the attribute in the list.
* @return The local name of the attribute as a string, or null if there
* is no attribute at that position.
*/
/*@Nullable*/
public String getURI(int index) {
if (names == null) {
return null;
}
if (index < 0 || index >= used) {
return null;
}
return names[index].getURI();
}
/**
* Get the type of an attribute (by position). This is a SAX2 method,
* so it gets the type name as a DTD attribute type, mapped from the
* schema type code.
*
* @param index The position of the attribute in the list.
* @return The attribute type as a string ("NMTOKEN" for an
* enumeration, and "CDATA" if no declaration was
* read), or null if there is no attribute at
* that position.
*/
/*@NotNull*/
public String getType(int index) {
int typeCode = getTypeAnnotation(index).getFingerprint();
switch (typeCode) {
case StandardNames.XS_ID:
return "ID";
case StandardNames.XS_IDREF:
return "IDREF";
case StandardNames.XS_NMTOKEN:
return "NMTOKEN";
case StandardNames.XS_ENTITY:
return "ENTITY";
case StandardNames.XS_IDREFS:
return "IDREFS";
case StandardNames.XS_NMTOKENS:
return "NMTOKENS";
case StandardNames.XS_ENTITIES:
return "ENTITIES";
default:
return "CDATA";
}
}
/**
* Get the type of an attribute (by name).
*
* @param uri The namespace uri of the attribute.
* @param localname The local name of the attribute.
* @return The index position of the attribute
*/
/*@Nullable*/
public String getType(String uri, String localname) {
int index = findByName(uri, localname);
return (index < 0 ? null : getType(index));
}
/**
* Get the value of an attribute (by position).
*
* @param index The position of the attribute in the list.
* @return The attribute value as a string, or null if
* there is no attribute at that position.
*/
/*@Nullable*/
public String getValue(int index) {
if (values == null) {
return null;
}
if (index < 0 || index >= used) {
return null;
}
return values[index];
}
/**
* Get the value of an attribute (by name).
*
* @param uri The namespace uri of the attribute.
* @param localname The local name of the attribute.
* @return The index position of the attribute
*/
/*@Nullable*/
public String getValue(String uri, String localname) {
int index = findByName(uri, localname);
return (index < 0 ? null : getValue(index));
}
/**
* Get the attribute value using its fingerprint
*/
/*@Nullable*/
public String getValueByFingerprint(int fingerprint) {
int index = findByFingerprint(fingerprint);
return (index < 0 ? null : getValue(index));
}
/**
* Get the index of an attribute, from its lexical QName
*
* @param qname The lexical QName of the attribute. The prefix must match.
* @return The index position of the attribute
*/
public int getIndex(/*@NotNull*/ String qname) {
if (names == null) {
return -1;
}
if (qname.indexOf(':') < 0) {
return findByName("", qname);
}
// Searching using prefix+localname is not recommended, but SAX allows it...
String[] parts;
try {
parts = NameChecker.getQNameParts(qname);
} catch (QNameException err) {
return -1;
}
String prefix = parts[0];
if (prefix.isEmpty()) {
return findByName("", qname);
} else {
String localName = parts[1];
for (int i = 0; i < used; i++) {
if (names[i] != null) {
String lname = names[i].getLocalPart();
String ppref = names[i].getPrefix();
if (localName.equals(lname) && prefix.equals(ppref)) {
return i;
}
}
}
return -1;
}
}
/**
* Get the index of an attribute (by name).
*
* @param uri The namespace uri of the attribute.
* @param localname The local name of the attribute.
* @return The index position of the attribute, or -1 if absent
*/
public int getIndex(String uri, String localname) {
return findByName(uri, localname);
}
/**
* Get the index, given the fingerprint.
* Return -1 if not found.
*/
public int getIndexByFingerprint(int fingerprint) {
return findByFingerprint(fingerprint);
}
/**
* Get the type of an attribute (by lexical QName).
*
* @param name The lexical QName of the attribute.
* @return The attribute type as a string (e.g. "NMTOKEN", or
* "CDATA" if no declaration was read).
*/
/*@NotNull*/
public String getType(/*@NotNull*/ String name) {
int index = getIndex(name);
return getType(index);
}
/**
* Get the value of an attribute (by lexnical QName).
*
* @param name The attribute name (a lexical QName).
* The prefix must match the prefix originally used. This method is defined in SAX, but is
* not recommended except where the prefix is null.
*/
/*@Nullable*/
public String getValue(/*@NotNull*/ String name) {
int index = getIndex(name);
return getValue(index);
}
/**
* Ask whether the attribute collection contains any attributes
* in a specified namespace
*
* @param uri the specified namespace
* @return true if there are one or more attributes in this namespace
*/
public boolean hasAttributeInNamespace(String uri) {
if (names == null || config == null) {
return false; // indicates an empty attribute set
}
for (int i = 0; i < used; i++) {
if (names[i] != null && names[i].hasURI(uri)) {
return true;
}
}
return false;
}
/**
* Find an attribute by expanded name
*
* @param uri the namespace uri
* @param localName the local name
* @return the index of the attribute, or -1 if absent
*/
private int findByName(String uri, String localName) {
if (names == null || config == null) {
return -1; // indicates an empty attribute set
}
for (int i = 0; i < used; i++) {
if (names[i] != null && names[i].hasURI(uri) && localName.equals(names[i].getLocalPart())) {
return i;
}
}
return -1;
}
/**
* Find an attribute by fingerprint
*
* @param fingerprint the fingerprint representing the name of the required attribute
* @return the index of the attribute, or -1 if absent
*/
private int findByFingerprint(int fingerprint) {
if (names == null || config == null) {
return -1;
}
NamePool pool = config.getNamePool();
for (int i = 0; i < used; i++) {
NodeName nn = names[i];
if (nn != null) {
if (nn.hasFingerprint()) {
if (nn.getFingerprint() == fingerprint) {
return i;
}
} else {
if (nn.hasURI(pool.getURI(fingerprint)) && nn.getLocalPart().equals(pool.getLocalName(fingerprint))) {
return i;
}
}
}
}
return -1;
}
/**
* Find an attribute by node name
*
* @param nodeName the name of the required attribute
* @return the index of the attribute, or -1 if absent
*/
public int findByNodeName(/*@NotNull*/ NodeName nodeName) {
if (props == null || config == null) {
return -1;
}
if (nodeName.hasFingerprint()) {
return findByFingerprint(nodeName.getFingerprint());
} else {
return findByName(nodeName.getURI(), nodeName.getLocalPart());
}
}
/**
* Determine whether a given attribute has the is-ID property set
*/
public boolean isId(int index) {
try {
return !isDeleted(index) &&
(StandardNames.XML_ID_NAME.equals(names[index]) ||
(getProperties(index) & ReceiverOptions.IS_ID) != 0 ||
getTypeAnnotation(index).isIdType());
} catch (MissingComponentException e) {
return false;
}
}
/**
* Determine whether a given attribute has the is-idref property set
*/
public boolean isIdref(int index) {
try {
return getTypeAnnotation(index).isIdRefType();
} catch (MissingComponentException e) {
return false;
}
}
/**
* Delete the attribute at a given index position. Note that the index position will not be reused, to ensure
* that any new attributes added to the element have a distinct identity. Instead, the slot occupied
* by the attribute is nilled out.
*
* @param index The index position of the attribute to be removed
*/
public void removeAttribute(int index) {
names[index] = null;
props[index] = -1;
values[index] = null;
locations[index] = null;
if (types != null) {
types[index] = null;
}
}
/**
* Test whether the attribute at a given index has been deleted
*
* @param index the index position of the (ex-) attribute
* @return true if the attribute has been deleted
*/
public boolean isDeleted(int index) {
return names[index] == null;
}
/**
* Rename an attribute
*
* @param index the index position of the attribute
* @param newName the namecode of the new name
*/
public void renameAttribute(int index, NodeName newName) {
names[index] = newName;
if (types != null) {
types[index] = BuiltInAtomicType.UNTYPED_ATOMIC;
}
}
/**
* Replace the value of an attribute
*
* @param index position of the attribute
* @param newValue the new string value of the attribute
*/
public void replaceAttribute(int index, /*@NotNull*/ CharSequence newValue) {
values[index] = newValue.toString();
}
/**
* Set the type annotation of an attribute
*
* @param index the index position of the attribute node
* @param type the new type for the attribute
*/
public void setTypeAnnotation(int index, SimpleType type) {
if (type.equals(BuiltInAtomicType.UNTYPED_ATOMIC)) {
if (types != null) {
types[index] = type;
}
} else {
if (types == null) {
types = new SimpleType[names.length];
Arrays.fill(types, BuiltInAtomicType.UNTYPED_ATOMIC);
types[index] = type;
} else {
types[index] = type;
}
}
}
/**
* Swap two attributes (used for sorting)
*
* @param i the position of one value
* @param j the position of the other value
*/
public void swap(int i, int j) {
NodeName n = names[i];
names[i] = names[j];
names[j] = n;
if (types != null) {
SimpleType s = types[i];
types[i] = types[j];
types[j] = s;
}
String c = values[i];
values[i] = values[j];
values[j] = c;
int p = props[i];
props[i] = props[j];
props[j] = p;
Location l = locations[i];
locations[i] = locations[j];
locations[j] = l;
}
}