org.jsoup.nodes.Attributes Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of driver-cql-shaded Show documentation
Show all versions of driver-cql-shaded Show documentation
A Shaded CQL ActivityType driver for http://nosqlbench.io/
package org.jsoup.nodes;
import org.jsoup.SerializationException;
import org.jsoup.helper.Validate;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.jsoup.internal.Normalizer.lowerCase;
/**
* The attributes of an Element.
*
* Attributes are treated as a map: there can be only one value associated with an attribute key/name.
*
*
* Attribute name and value comparisons are generally case sensitive. By default for HTML, attribute names are
* normalized to lower-case on parsing. That means you should use lower-case strings when referring to attributes by
* name.
*
*
* @author Jonathan Hedley, [email protected]
*/
public class Attributes implements Iterable, Cloneable {
protected static final String dataPrefix = "data-";
private static final int InitialCapacity = 4; // todo - analyze Alexa 1MM sites, determine best setting
// manages the key/val arrays
private static final int GrowthFactor = 2;
private static final String[] Empty = {};
static final int NotFound = -1;
private static final String EmptyString = "";
private int size = 0; // number of slots used (not capacity, which is keys.length
String[] keys = Empty;
String[] vals = Empty;
// check there's room for more
private void checkCapacity(int minNewSize) {
Validate.isTrue(minNewSize >= size);
int curSize = keys.length;
if (curSize >= minNewSize)
return;
int newSize = curSize >= InitialCapacity ? size * GrowthFactor : InitialCapacity;
if (minNewSize > newSize)
newSize = minNewSize;
keys = copyOf(keys, newSize);
vals = copyOf(vals, newSize);
}
// simple implementation of Arrays.copy, for support of Android API 8.
private static String[] copyOf(String[] orig, int size) {
final String[] copy = new String[size];
System.arraycopy(orig, 0, copy, 0,
Math.min(orig.length, size));
return copy;
}
int indexOfKey(String key) {
Validate.notNull(key);
for (int i = 0; i < size; i++) {
if (key.equals(keys[i]))
return i;
}
return NotFound;
}
private int indexOfKeyIgnoreCase(String key) {
Validate.notNull(key);
for (int i = 0; i < size; i++) {
if (key.equalsIgnoreCase(keys[i]))
return i;
}
return NotFound;
}
// we track boolean attributes as null in values - they're just keys. so returns empty for consumers
static String checkNotNull(String val) {
return val == null ? EmptyString : val;
}
/**
Get an attribute value by key.
@param key the (case-sensitive) attribute key
@return the attribute value if set; or empty string if not set (or a boolean attribute).
@see #hasKey(String)
*/
public String get(String key) {
int i = indexOfKey(key);
return i == NotFound ? EmptyString : checkNotNull(vals[i]);
}
/**
* Get an attribute's value by case-insensitive key
* @param key the attribute name
* @return the first matching attribute value if set; or empty string if not set (ora boolean attribute).
*/
public String getIgnoreCase(String key) {
int i = indexOfKeyIgnoreCase(key);
return i == NotFound ? EmptyString : checkNotNull(vals[i]);
}
// adds without checking if this key exists
private void add(String key, String value) {
checkCapacity(size + 1);
keys[size] = key;
vals[size] = value;
size++;
}
/**
* Set a new attribute, or replace an existing one by key.
* @param key case sensitive attribute key
* @param value attribute value
* @return these attributes, for chaining
*/
public Attributes put(String key, String value) {
int i = indexOfKey(key);
if (i != NotFound)
vals[i] = value;
else
add(key, value);
return this;
}
void putIgnoreCase(String key, String value) {
int i = indexOfKeyIgnoreCase(key);
if (i != NotFound) {
vals[i] = value;
if (!keys[i].equals(key)) // case changed, update
keys[i] = key;
}
else
add(key, value);
}
/**
* Set a new boolean attribute, remove attribute if value is false.
* @param key case insensitive attribute key
* @param value attribute value
* @return these attributes, for chaining
*/
public Attributes put(String key, boolean value) {
if (value)
putIgnoreCase(key, null);
else
remove(key);
return this;
}
/**
Set a new attribute, or replace an existing one by key.
@param attribute attribute with case sensitive key
@return these attributes, for chaining
*/
public Attributes put(Attribute attribute) {
Validate.notNull(attribute);
put(attribute.getKey(), attribute.getValue());
attribute.parent = this;
return this;
}
// removes and shifts up
private void remove(int index) {
Validate.isFalse(index >= size);
int shifted = size - index - 1;
if (shifted > 0) {
System.arraycopy(keys, index + 1, keys, index, shifted);
System.arraycopy(vals, index + 1, vals, index, shifted);
}
size--;
keys[size] = null; // release hold
vals[size] = null;
}
/**
Remove an attribute by key. Case sensitive.
@param key attribute key to remove
*/
public void remove(String key) {
int i = indexOfKey(key);
if (i != NotFound)
remove(i);
}
/**
Remove an attribute by key. Case insensitive.
@param key attribute key to remove
*/
public void removeIgnoreCase(String key) {
int i = indexOfKeyIgnoreCase(key);
if (i != NotFound)
remove(i);
}
/**
Tests if these attributes contain an attribute with this key.
@param key case-sensitive key to check for
@return true if key exists, false otherwise
*/
public boolean hasKey(String key) {
return indexOfKey(key) != NotFound;
}
/**
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 hasKeyIgnoreCase(String key) {
return indexOfKeyIgnoreCase(key) != NotFound;
}
/**
Get the number of attributes in this set.
@return size
*/
public int size() {
return 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;
checkCapacity(size + incoming.size);
for (Attribute attr : incoming) {
// todo - should this be case insensitive?
put(attr);
}
}
public Iterator iterator() {
return new Iterator() {
int i = 0;
@Override
public boolean hasNext() {
return i < size;
}
@Override
public Attribute next() {
final Attribute attr = new Attribute(keys[i], vals[i], Attributes.this);
i++;
return attr;
}
@Override
public void remove() {
Attributes.this.remove(--i); // next() advanced, so rewind
}
};
}
/**
Get the attributes as a List, for iteration.
@return an view of the attributes as an unmodifialbe List.
*/
public List asList() {
ArrayList list = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
Attribute attr = vals[i] == null ?
new BooleanAttribute(keys[i]) : // deprecated class, but maybe someone still wants it
new Attribute(keys[i], vals[i], Attributes.this);
list.add(attr);
}
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 dataset() {
return new Dataset(this);
}
/**
Get the HTML representation of these attributes.
@return HTML
@throws SerializationException if the HTML representation of the attributes cannot be constructed.
*/
public String html() {
StringBuilder accum = new StringBuilder();
try {
html(accum, (new Document("")).outputSettings()); // output settings a bit funky, but this html() seldom used
} catch (IOException e) { // ought never happen
throw new SerializationException(e);
}
return accum.toString();
}
final void html(final Appendable accum, final Document.OutputSettings out) throws IOException {
final int sz = size;
for (int i = 0; i < sz; i++) {
// inlined from Attribute.html()
final String key = keys[i];
final String val = vals[i];
accum.append(' ').append(key);
// collapse checked=null, checked="", checked=checked; write out others
if (!Attribute.shouldCollapseAttribute(key, val, out)) {
accum.append("=\"");
Entities.escape(accum, val == null ? EmptyString : val, out, true, false, false);
accum.append('"');
}
}
}
@Override
public String toString() {
return html();
}
/**
* Checks if these attributes are equal to another set of attributes, by comparing the two sets
* @param o attributes to compare with
* @return if both sets of attributes have the same content
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Attributes that = (Attributes) o;
if (size != that.size) return false;
if (!Arrays.equals(keys, that.keys)) return false;
return Arrays.equals(vals, that.vals);
}
/**
* Calculates the hashcode of these attributes, by iterating all attributes and summing their hashcodes.
* @return calculated hashcode
*/
@Override
public int hashCode() {
int result = size;
result = 31 * result + Arrays.hashCode(keys);
result = 31 * result + Arrays.hashCode(vals);
return result;
}
@Override
public Attributes clone() {
Attributes clone;
try {
clone = (Attributes) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
clone.size = size;
keys = copyOf(keys, size);
vals = copyOf(vals, size);
return clone;
}
/**
* Internal method. Lowercases all keys.
*/
public void normalize() {
for (int i = 0; i < size; i++) {
keys[i] = lowerCase(keys[i]);
}
}
private static class Dataset extends AbstractMap {
private final Attributes attributes;
private Dataset(Attributes attributes) {
this.attributes = attributes;
}
@Override
public Set> entrySet() {
return new EntrySet();
}
@Override
public String put(String key, String value) {
String dataKey = dataKey(key);
String oldValue = attributes.hasKey(dataKey) ? attributes.get(dataKey) : null;
attributes.put(dataKey, value);
return oldValue;
}
private class EntrySet extends AbstractSet> {
@Override
public Iterator> iterator() {
return new DatasetIterator();
}
@Override
public int size() {
int count = 0;
Iterator iter = new DatasetIterator();
while (iter.hasNext())
count++;
return count;
}
}
private class DatasetIterator implements Iterator> {
private Iterator attrIter = attributes.iterator();
private Attribute attr;
public boolean hasNext() {
while (attrIter.hasNext()) {
attr = attrIter.next();
if (attr.isDataAttribute()) return true;
}
return false;
}
public Entry 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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy