diff options
Diffstat (limited to 'server/src/org/jsoup/nodes/Attributes.java')
-rw-r--r-- | server/src/org/jsoup/nodes/Attributes.java | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/server/src/org/jsoup/nodes/Attributes.java b/server/src/org/jsoup/nodes/Attributes.java new file mode 100644 index 0000000000..9436750fc9 --- /dev/null +++ b/server/src/org/jsoup/nodes/Attributes.java @@ -0,0 +1,249 @@ +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, jonathan@hedley.net + */ +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; + } +} |