com.itextpdf.styledxmlparser.jsoup.nodes.Attribute Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of styled-xml-parser Show documentation
Show all versions of styled-xml-parser Show documentation
Styled XML parser is used by iText modules to parse HTML and XML
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2024 Apryse Group NV
Authors: Apryse Software.
This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
package com.itextpdf.styledxmlparser.jsoup.nodes;
import com.itextpdf.styledxmlparser.jsoup.SerializationException;
import com.itextpdf.styledxmlparser.jsoup.internal.StringUtil;
import com.itextpdf.styledxmlparser.jsoup.helper.Validate;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
/**
A single key + value attribute. (Only used for presentation.)
*/
public class Attribute implements Map.Entry, Cloneable {
private static final String[] booleanAttributes = {
"allowfullscreen", "async", "autofocus", "checked", "compact", "declare", "default", "defer", "disabled",
"formnovalidate", "hidden", "inert", "ismap", "itemscope", "multiple", "muted", "nohref", "noresize",
"noshade", "novalidate", "nowrap", "open", "readonly", "required", "reversed", "seamless", "selected",
"sortable", "truespeed", "typemustmatch"
};
private String key;
private String val;
Attributes parent; // used to update the holding Attributes when the key / value is changed via this interface
/**
* Create a new attribute from unencoded (raw) key and value.
* @param key attribute key; case is preserved.
* @param value attribute value (may be null)
* @see #createFromEncoded
*/
public Attribute(String key, String value) {
this(key, value, null);
}
/**
* Create a new attribute from unencoded (raw) key and value.
* @param key attribute key; case is preserved.
* @param val attribute value (may be null)
* @param parent the containing Attributes (this Attribute is not automatically added to said Attributes)
* @see #createFromEncoded*/
public Attribute(String key, String val, Attributes parent) {
Validate.notNull(key);
key = key.trim();
Validate.notEmpty(key); // trimming could potentially make empty, so validate here
this.key = key;
this.val = val;
this.parent = parent;
}
/**
Get the attribute key.
@return the attribute key
*/
public String getKey() {
return key;
}
/**
Set the attribute key; case is preserved.
@param key the new key; must not be null
*/
public void setKey(String key) {
Validate.notNull(key);
key = key.trim();
Validate.notEmpty(key); // trimming could potentially make empty, so validate here
if (parent != null) {
int i = parent.indexOfKey(this.key);
if (i != Attributes.NotFound)
parent.keys[i] = key;
}
this.key = key;
}
/**
Get the attribute value. Will return an empty string if the value is not set.
@return the attribute value
*/
public String getValue() {
return Attributes.checkNotNull(val);
}
/**
* Check if this Attribute has a value. Set boolean attributes have no value.
* @return if this is a boolean attribute / attribute without a value
*/
public boolean hasDeclaredValue() {
return val != null;
}
/**
Set the attribute value.
@param val the new attribute value; must not be null
*/
public String setValue(String val) {
String oldVal = this.val;
if (parent != null) {
oldVal = parent.get(this.key); // trust the container more
int i = parent.indexOfKey(this.key);
if (i != Attributes.NotFound)
parent.vals[i] = val;
}
this.val = val;
return Attributes.checkNotNull(oldVal);
}
/**
Get the HTML representation of this attribute; e.g. {@code href="index.html"}.
@return HTML
*/
public String html() {
StringBuilder sb = StringUtil.borrowBuilder();
try {
html(sb, (new Document("")).outputSettings());
} catch(IOException exception) {
throw new SerializationException(exception);
}
return StringUtil.releaseBuilder(sb);
}
protected static void html(String key, String val, Appendable accum, Document.OutputSettings out) throws IOException {
accum.append(key);
if (!shouldCollapseAttribute(key, val, out)) {
accum.append("=\"");
Entities.escape(accum, Attributes.checkNotNull(val) , out, true, false, false);
accum.append('"');
}
}
protected void html(Appendable accum, Document.OutputSettings out) throws IOException {
html(key, val, accum, out);
}
/**
Get the string representation of this attribute, implemented as {@link #html()}.
@return string
*/
@Override
public String toString() {
return html();
}
/**
* Create a new Attribute from an unencoded key and a HTML attribute encoded value.
* @param unencodedKey assumes the key is not encoded, as can be only run of simple \w chars.
* @param encodedValue HTML attribute encoded value
* @return attribute
*/
public static Attribute createFromEncoded(String unencodedKey, String encodedValue) {
String value = Entities.unescape(encodedValue, true);
return new Attribute(unencodedKey, value, null); // parent will get set when Put
}
protected boolean isDataAttribute() {
return isDataAttribute(key);
}
protected static boolean isDataAttribute(String key) {
return key.startsWith(Attributes.dataPrefix) && key.length() > Attributes.dataPrefix.length();
}
/**
* Collapsible if it's a boolean attribute and value is empty or same as name
*
* @param out output settings
* @return Returns whether collapsible or not
*/
protected final boolean shouldCollapseAttribute(Document.OutputSettings out) {
return shouldCollapseAttribute(key, val, out);
}
protected static boolean shouldCollapseAttribute(final String key, final String val, final Document.OutputSettings out) {
return (
out.syntax() == Document.OutputSettings.Syntax.html &&
(val == null || (val.isEmpty() || val.equalsIgnoreCase(key)) && Attribute.isBooleanAttribute(key)));
}
/**
* Checks if this attribute name is defined as a boolean attribute in HTML5
*/
protected static boolean isBooleanAttribute(final String key) {
return Arrays.binarySearch(booleanAttributes, key) >= 0;
}
@Override
public boolean equals(Object o) { // note parent not considered
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Attribute attribute = (Attribute) o;
if (key != null ? !key.equals(attribute.key) : attribute.key != null) return false;
return val != null ? val.equals(attribute.val) : attribute.val == null;
}
@Override
public int hashCode() { // note parent not considered
int result = key != null ? key.hashCode() : 0;
result = 31 * result + (val != null ? val.hashCode() : 0);
return result;
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}