package org.jsoup.nodes;
import org.jsoup.helper.Validate;
import java.util.*;
/**
* The attributes of an Element.
* <p/>
* Attributes are treated as a map: there can be only one value associated with an attribute key.
* <p/>
* Attribute key and value comparisons are done case insensitively, and keys are normalised to
* lower-case.
*
* @author Jonathan Hedley, [email protected]
*/
public class Attributes implements Iterable<Attribute>, Cloneable {
protected static final String dataPrefix = "data-";
private LinkedHashMap<String, Attribute> attributes = null;
// linked hash map to preserve insertion order.
// null be default as so many elements have no attributes -- saves a good chunk of memory
/**
Get an attribute value by key.
@param key the attribute key
@return the attribute value if set; or empty string if not set.
@see #hasKey(String)
*/
public String get(String key) {
Validate.notEmpty(key);
if (attributes == null)
return "";
Attribute attr = attributes.get(key.toLowerCase());
return attr != null ? attr.getValue() : "";
}
/**
Set a new attribute, or replace an existing one by key.
@param key attribute key
@param value attribute value
*/
public void put(String key, String value) {
Attribute attr = new Attribute(key, value);
put(attr);
}
/**
Set a new attribute, or replace an existing one by key.
@param attribute attribute
*/
public void put(Attribute attribute) {
Validate.notNull(attribute);
if (attributes == null)
attributes = new LinkedHashMap<String, Attribute>(2);
attributes.put(attribute.getKey(), attribute);
}
/**
Remove an attribute by key.
@param key attribute key to remove
*/
public void remove(String key) {
Validate.notEmpty(key);
if (attributes == null)
return;
attributes.remove(key.toLowerCase());
}
/**
Tests if these attributes contain an attribute with this key.
@param key key to check for
@return true if key exists, false otherwise
*/
public boolean hasKey(String key) {
return attributes != null && attributes.containsKey(key.toLowerCase());
}
/**
Get the number of attributes in this set.
@return size
*/
public int size() {
if (attributes == null)
return 0;
return attributes.size();
}
/**
Add all the attributes from the incoming set to this set.
@param incoming attributes to add to these attributes.
*/
public void addAll(Attributes incoming) {
if (incoming.size() == 0)
return;
if (attributes == null)
attributes = new LinkedHashMap<String, Attribute>(incoming.size());
attributes.putAll(incoming.attributes);
}
public Iterator<Attribute> iterator() {
return asList().iterator();
}
/**
Get the attributes as a List, for iteration. Do not modify the keys of the attributes via this view, as changes
to keys will not be recognised in the containing set.
@return an view of the attributes as a List.
*/
public List<Attribute> asList() {
if (attributes == null)
return Collections.emptyList();
List<Attribute> list = new ArrayList<Attribute>(attributes.size());
for (Map.Entry<String, Attribute> entry : attributes.entrySet()) {
list.add(entry.getValue());
}
return Collections.unmodifiableList(list);
}
/**
* Retrieves a filtered view of attributes that are HTML5 custom data attributes; that is, attributes with keys
* starting with {@code data-}.
* @return map of custom data attributes.
*/
public Map<String, String> dataset() {
return new Dataset();
}
/**
Get the HTML representation of these attributes.
@return HTML
*/
public String html() {
StringBuilder accum = new StringBuilder();
html(accum, (new Document("")).outputSettings()); // output settings a bit funky, but this html() seldom used
return accum.toString();
}
void html(StringBuilder accum, Document.OutputSettings out) {
if (attributes == null)
return;
for (Map.Entry<String, Attribute> entry : attributes.entrySet()) {
Attribute attribute = entry.getValue();
accum.append(" ");
attribute.html(accum, out);
}
}
public String toString() {
return html();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Attributes)) return false;
Attributes that = (Attributes) o;
if (attributes != null ? !attributes.equals(that.attributes) : that.attributes != null) return false;
return true;
}
@Override
public int hashCode() {
return attributes != null ? attributes.hashCode() : 0;
}
@Override
public Attributes clone() {
if (attributes == null)
return new Attributes();
Attributes clone;
try {
clone = (Attributes) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
clone.attributes = new LinkedHashMap<String, Attribute>(attributes.size());
for (Attribute attribute: this)
clone.attributes.put(attribute.getKey(), attribute.clone());
return clone;
}
private class Dataset extends AbstractMap<String, String> {
private Dataset() {
if (attributes == null)
attributes = new LinkedHashMap<String, Attribute>(2);
}
public Set<Entry<String, String>> entrySet() {
return new EntrySet();
}
@Override
public String put(String key, String value) {
String dataKey = dataKey(key);
String oldValue = hasKey(dataKey) ? attributes.get(dataKey).getValue() : null;
Attribute attr = new Attribute(dataKey, value);
attributes.put(dataKey, attr);
return oldValue;
}
private class EntrySet extends AbstractSet<Map.Entry<String, String>> {
public Iterator<Map.Entry<String, String>> iterator() {
return new DatasetIterator();
}
public int size() {
int count = 0;
Iterator iter = new DatasetIterator();
while (iter.hasNext())
count++;
return count;
}
}
private class DatasetIterator implements Iterator<Map.Entry<String, String>> {
private Iterator<Attribute> attrIter = attributes.values().iterator();
private Attribute attr;
public boolean hasNext() {
while (attrIter.hasNext()) {
attr = attrIter.next();
if (attr.isDataAttribute()) return true;
}
return false;
}
public Entry<String, String> next() {
return new Attribute(attr.getKey().substring(dataPrefix.length()), attr.getValue());
}
public void remove() {
attributes.remove(attr.getKey());
}
}
}
private static String dataKey(String key) {
return dataPrefix + key;
}
}
|