![JAR search and dependency download from the Maven repository](/logo.png)
com.adobe.granite.ui.components.AttrBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2012 Adobe
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe
* and its suppliers and are protected by all applicable intellectual
* property laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe.
**************************************************************************/
package com.adobe.granite.ui.components;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.adobe.granite.xss.XSSAPI;
/**
* A builder to generate HTML attributes. This builder is designed to be secured
* using {@link XSSAPI}. It will encode the value automatically. If the value is
* {@code null}, it will be ignored.
*/
public class AttrBuilder {
private final HttpServletRequest req;
private final XSSAPI xssAPI;
@Nonnull
private Map data = new LinkedHashMap();
private Map encodings = new LinkedHashMap<>();
private Set classes = new HashSet();
private enum Encoding {
HREF, HTML_ATTR
}
private static final Logger LOG = LoggerFactory.getLogger(AttrBuilder.class);
public AttrBuilder(@Nonnull HttpServletRequest req, @Nonnull XSSAPI xssAPI) {
this.req = req;
this.xssAPI = xssAPI;
}
/**
* Validate a string which should contain a valid HTML attribute name.
* returns an empty string if the source is invalid
* https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#attributes-0 index: 8.1.2.3 Attributes
* @param source the HTML attribute name
* @return a sanitised attribute name possibly empty.
*/
private String getValidHTMLAttrName(String source) {
if (source != null) {
String name = source.trim();
if (name.length() > 0 && name.matches("[^='/> \"\n\t]+")) {
return name;
}
}
LOG.warn("Unable to get a valid attribute name from the input '" + source + "'");
return "";
}
/**
* Gets the raw {@link Map} of attributes, with un-encoded values
*
* @return {@link Map} of attributes
*/
@Nonnull
public Map getData() {
return data;
}
/**
* Adds relationship. Currently it is implemented as {@code class} attribute.
*
* @param value
* the relationship to add
*/
public void addRel(@CheckForNull String value) {
addClass(value);
}
/**
* Adds {@code class} attribute with the given value.
*
* @param value
* the class attribute to add
*/
public void addClass(@CheckForNull String value) {
if (StringUtils.isBlank(value)) {
return;
}
if (classes.add(value)) {
add("class", value);
}
}
/**
* Adds an attribute that behave like {@code href} attribute. i.e. the value
* will be prepended with context path (if absolute path) and checked using
* {@link XSSAPI#getValidHref(String)}.
*
* @param name
* the name of the attribute to add
* @param value
* the value of the specified attribute
*/
public void addHref(@CheckForNull String name, @CheckForNull String value) {
if (value == null || StringUtils.isBlank(value) || StringUtils.isBlank(name)) {
return;
}
if (value.startsWith("/")) {
value = req.getContextPath() + value;
}
data.put(name, value);
encodings.put(name, Encoding.HREF);
}
/**
* Adds {@code disabled} attribute.
*
* @param disabled
* the boolean value of the {@code disabled} attribute
*/
public void addDisabled(boolean disabled) {
this.addBoolean("disabled", disabled);
}
/**
* Adds {@code checked} attribute.
*
* @param checked
* the boolean value of the {@code checked} attribute
*/
public void addChecked(boolean checked) {
this.addBoolean("checked", checked);
}
/**
* Adds {@code selected} attribute.
*
* @param selected
* the boolean value of the {@code selected} attribute
*/
public void addSelected(boolean selected) {
this.addBoolean("selected", selected);
}
/**
* Adds {@code multiple} attribute.
*
* @param multiple
* the boolean value of the {@code multiple} attribute
*/
public void addMultiple(boolean multiple) {
this.addBoolean("multiple", multiple);
}
/**
* Adds boolean attribute (behaves like {@code disabled}) for the given name.
*
* When the given value is {@code true}, it will be printed as "disabled=''",
* instead of "disabled='true'". When the given value is {@code false}, it will
* NOT be printed, instead of "disabled='false'".
*
* @param name
* the name of the boolean attribute to add
* @param value
* the boolean value of the attribute
*/
public void addBoolean(@CheckForNull String name, boolean value) {
if (!value) {
return;
}
add(name, "");
}
/**
* Adds the given name as {@code data-*} attribute.
*
* @param name
* the name of the {@code data-*} attribute to add
* @param value
* the value of the attribute
*/
public void addOther(@CheckForNull String name, @CheckForNull String value) {
String key = getValidHTMLAttrName(name);
if (StringUtils.isBlank(key)) {
return;
}
add("data-" + key, value);
}
/**
* Adds the given data as {@code data-*} attributes. Entries with keys specified
* in exclusions parameter or having namespace (e.g. "jcr:primaryType") will be
* excluded.
*
* @param data
* the map containing key/value pairs to add as {@code data-*}
* attributes
* @param exclusions
* the keys which must not be added as {@code data-*} attributes
*/
public void addOthers(@Nonnull Map data, @Nonnull String... exclusions) {
List blacklisted = Arrays.asList(exclusions);
for (Entry e : data.entrySet()) {
String key = e.getKey();
if (key.indexOf(":") >= 0) {
continue;
}
if (blacklisted.indexOf(key) >= 0) {
continue;
}
Object value = e.getValue();
if (value.getClass().isArray()) {
for (Object o : (Object[]) value) {
addOther(key, o.toString());
}
} else {
addOther(key, value.toString());
}
}
}
/**
* Adds attribute with the given name. The value will be added to existing
* attribute using space-delimited convention. e.g. class="class1 class2"
*
* @param name
* the name of the attribute to add
* @param value
* the boolean value of the attribute
*/
@SuppressWarnings("null")
public void add(@CheckForNull String name, @CheckForNull Boolean value) {
if (value == null || name == null || StringUtils.isBlank(name)) {
return;
}
addNoCheck(name, value.toString());
}
/**
* Adds attribute with the given name. The value will be added to existing
* attribute using space-delimited convention. e.g. class="class1 class2"
*
* @param name
* the name of the attribute to add
* @param value
* the integer value of the attribute
*/
@SuppressWarnings("null")
public void add(@CheckForNull String name, @CheckForNull Integer value) {
if (value == null || name == null || StringUtils.isBlank(name)) {
return;
}
addNoCheck(name, value.toString());
}
/**
* Adds attribute with the given name. The value will be added to existing
* attribute using space-delimited convention. e.g. class="class1 class2"
*
* @param name
* the name of the attribute to add
* @param value
* the double value of the attribute
*/
@SuppressWarnings("null")
public void add(@CheckForNull String name, @CheckForNull Double value) {
if (value == null || name == null || StringUtils.isBlank(name)) {
return;
}
addNoCheck(name, value.toString());
}
/**
* Adds attribute with the given name. The value will be added to existing
* attribute using space-delimited convention. e.g. class="class1 class2"
*
* @param name
* the name of the attribute to add
* @param value
* the string value of the attribute
*/
public void add(@CheckForNull String name, @CheckForNull String value) {
if (value == null || name == null || StringUtils.isBlank(name)) {
return;
}
addNoCheck(name, value);
encodings.put(name, Encoding.HTML_ATTR);
}
/**
* Sets attribute with the given name. Existing value previously set will be
* replaced by the given value.
*
* @param name
* the name of the attribute to set or replace (if exists)
* @param value
* the string value of the attribute
*/
public void set(@CheckForNull String name, @CheckForNull String value) {
if (value == null || name == null || StringUtils.isBlank(name)) {
return;
}
data.put(name, value);
encodings.put(name, Encoding.HTML_ATTR);
}
private void addNoCheck(@Nonnull String name, @Nonnull String v) {
if (data.containsKey(name)) {
v = data.get(name) + " " + v;
}
data.put(name, v);
}
/**
* Returns {@code true} if there is no attribute in this builder, {@code false}
* otherwise.
*
* @return {@code true} if there is no attribute in this builder, {@code false}
* otherwise
*/
public boolean isEmpty() {
return data.isEmpty();
}
/**
* Builds the attributes in the form of {@code =''*}.
*
* @return the string containing the built attributes
*/
public String build() {
try {
StringWriter out = new StringWriter();
build(out);
return out.toString();
} catch (IOException impossible) {
throw new RuntimeException(impossible);
}
}
/**
* Builds the attributes in the form of {@code =''*}*.
*
* @param out
* the writer
* @throws IOException
* in case there's an error when appending to the writer
*/
public void build(@Nonnull Writer out) throws IOException {
for (Entry e : data.entrySet()) {
String key = e.getKey();
String value = e.getValue();
Encoding encoding = encodings.get(key);
if (encoding != null && value.length() > 0) {
switch (encoding) {
case HREF:
value = xssAPI.getValidHref(value);
break;
default:
value = xssAPI.encodeForHTMLAttr(value);
break;
}
}
out.append(" ").append(key).append("=\"").append(value).append("\"");
}
}
@Override
public String toString() {
return build();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy