![JAR search and dependency download from the Maven repository](/logo.png)
com.squarespace.template.plugins.platform.ContentFormatters Maven / Gradle / Ivy
/**
* Copyright (c) 2015 SQUARESPACE, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squarespace.template.plugins.platform;
import static com.squarespace.template.GeneralUtils.executeTemplate;
import static com.squarespace.template.GeneralUtils.isTruthy;
import static com.squarespace.template.GeneralUtils.loadResource;
import static com.squarespace.template.plugins.PluginUtils.slugify;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DurationFormatUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.squarespace.template.Arguments;
import com.squarespace.template.ArgumentsException;
import com.squarespace.template.BaseFormatter;
import com.squarespace.template.CodeException;
import com.squarespace.template.CodeExecuteException;
import com.squarespace.template.Compiler;
import com.squarespace.template.Constants;
import com.squarespace.template.Context;
import com.squarespace.template.Formatter;
import com.squarespace.template.FormatterRegistry;
import com.squarespace.template.Instruction;
import com.squarespace.template.StringView;
import com.squarespace.template.SymbolTable;
import com.squarespace.template.Variable;
import com.squarespace.template.Variables;
import com.squarespace.template.plugins.PluginDateUtils;
import com.squarespace.template.plugins.PluginUtils;
import com.squarespace.template.plugins.platform.enums.RecordType;
/**
* Extracted from Commons library at commit ab4ba7a6f2b872a31cb6449ae9e96f5f5b30f471
*/
public class ContentFormatters implements FormatterRegistry {
@Override
public void registerFormatters(SymbolTable table) {
table.add(new AbsUrlFormatter(Constants.BASE_URL_KEY));
table.add(new AudioPlayerFormatter());
table.add(new CapitalizeFormatter());
table.add(new ChildImageMetaFormatter());
table.add(new ColorWeightFormatter());
table.add(new CoverImageMetaFormatter());
table.add(new HeightFormatter());
table.add(new HumanizeDurationFormatter());
table.add(new ImageFormatter());
table.add(new ImageColorFormatter());
table.add(new ImageMetaFormatter());
table.add(new ImageMetaSrcSetFormatter());
table.add(new ItemClassesFormatter());
table.add(new ResizedHeightForWidthFormatter());
table.add(new ResizedWidthForHeightFormatter());
table.add(new SqspThumbForHeightFormatter());
table.add(new SqspThumbForWidthFormatter());
table.add(new TimesinceFormatter());
table.add(new VideoFormatter());
table.add(new WidthFormatter());
}
public static class AbsUrlFormatter extends BaseFormatter {
private final String[] baseUrlKey;
public AbsUrlFormatter(String[] baseUrlKey) {
super("AbsUrl", false);
this.baseUrlKey = baseUrlKey;
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
String baseUrl = ctx.resolve(baseUrlKey).asText();
String value = var.node().asText();
var.set(baseUrl + "/" + value);
}
}
public static class AudioPlayerFormatter extends BaseFormatter {
private Instruction template;
public AudioPlayerFormatter() {
super("audio-player", false);
}
@Override
public void initialize(Compiler compiler) throws CodeException {
String source = loadResource(ContentFormatters.class, "audio-player.html");
template = compiler.compile(source).code();
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
var.set(executeTemplate(ctx, template, var.node(), true));
}
}
public static class CapitalizeFormatter extends BaseFormatter {
public CapitalizeFormatter() {
super("capitalize", false);
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
String text = var.node().asText();
var.set(text.toUpperCase());
}
}
private abstract static class ImageMetaBaseFormatter extends BaseFormatter {
ImageMetaBaseFormatter(String identifier) {
super(identifier, false);
}
protected void outputImageMeta(JsonNode image, StringBuilder buf) {
if (image.isMissingNode()) {
return;
}
String focalPoint = getFocalPoint(image);
String origSize = image.path("originalSize").asText();
String assetUrl = image.path("assetUrl").asText();
String altText = getAltTextFromContentItem(image);
if (isLicensedAssetPreview(image)) {
buf.append("data-licensed-asset-preview=\"true\"").append(" ");
}
buf.append("data-src=\"").append(assetUrl);
buf.append("\" data-image=\"").append(assetUrl);
buf.append("\" data-image-dimensions=\"");
buf.append(origSize);
buf.append("\" data-image-focal-point=\"");
buf.append(focalPoint);
buf.append("\" alt=\"");
PluginUtils.escapeHtmlAttribute(altText, buf);
buf.append("\" ");
}
}
public static class ChildImageMetaFormatter extends ImageMetaBaseFormatter {
public ChildImageMetaFormatter() {
super("child-image-meta");
}
@Override
public void validateArgs(Arguments args) throws ArgumentsException {
args.atMost(1);
int index = 0;
args.setOpaque(index);
if (args.count() == 1) {
try {
index = Integer.parseInt(args.first());
args.setOpaque(index);
} catch (NumberFormatException e) {
throw new ArgumentsException("expected an integer index, found '" + args.first() + "'");
}
}
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
int index = (Integer)args.getOpaque();
JsonNode child = var.node().path("items").path(index);
StringBuilder buf = new StringBuilder();
outputImageMeta(child, buf);
var.set(buf);
}
}
private static final Pattern VALID_COLOR = Pattern.compile("[abcdef0-9]{3,6}", Pattern.CASE_INSENSITIVE);
private static final int HALFBRIGHT = 0xFFFFFF / 2;
/**
* COLOR_WEIGHT
*/
public static class ColorWeightFormatter extends BaseFormatter {
public ColorWeightFormatter() {
super("color-weight", false);
}
/**
* Properly handle hex colors of length 3. Width of each channel needs to be expanded.
*/
private int color3(char c1, char c2, char c3) {
int n1 = PluginUtils.hexDigitToInt(c1);
int n2 = PluginUtils.hexDigitToInt(c2);
int n3 = PluginUtils.hexDigitToInt(c3);
return (n1 << 20) | (n1 << 16) | (n2 << 12) | (n2 << 8) | (n3 << 4) | n3;
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
String hex = var.node().asText();
hex = hex.replace("#", "");
if (!VALID_COLOR.matcher(hex).matches()) {
var.setMissing();
return;
}
int value = 0;
if (hex.length() == 3) {
value = color3(hex.charAt(0), hex.charAt(1), hex.charAt(2));
} else if (hex.length() == 6) {
value = Integer.parseInt(hex, 16);
}
String weight = (value > HALFBRIGHT) ? "light" : "dark";
var.set(weight);
}
};
private static String[] splitDimensions(JsonNode node) {
String val = node.asText();
String[] parts = StringUtils.split(val, 'x');
if (parts.length != 2) {
return null;
}
return parts;
}
private static String getFocalPoint(JsonNode media) {
String focalPoint = "0.5,0.5";
JsonNode node = media.path("mediaFocalPoint");
if (!node.isMissingNode()) {
focalPoint = node.path("x").asDouble() + "," + node.path("y").asDouble();
}
return focalPoint;
}
private static String getAltTextFromContentItem(JsonNode contentItemNode) {
JsonNode title = contentItemNode.path("title");
if (isTruthy(title)) {
return title.asText();
}
JsonNode body = contentItemNode.path("body");
if (isTruthy(body)) {
String text = PluginUtils.removeTags(body.asText());
if (text.length() > 0) {
return text;
}
}
JsonNode filename = contentItemNode.path("filename");
if (isTruthy(filename)) {
return filename.asText();
}
return "";
}
public static class HeightFormatter extends BaseFormatter {
public HeightFormatter() {
super("height", false);
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
String[] parts = splitDimensions(var.node());
if (parts == null || parts.length != 2) {
var.set("Invalid source parameter. Pass in 'originalSize'.");
} else {
int height = Integer.parseInt(parts[1]);
var.set(height);
}
}
};
/**
* HUMANIZE_DURATION
*/
public static class HumanizeDurationFormatter extends BaseFormatter {
public HumanizeDurationFormatter() {
super("humanizeDuration", false);
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
long duration = var.node().asLong();
var.set(DurationFormatUtils.formatDuration(duration, "m:ss"));
}
};
private static boolean isLicensedAssetPreview(JsonNode image) {
if (image.path("licensedAssetPreview").isObject()) {
return true;
}
return false;
}
public static class ImageFormatter extends BaseFormatter {
public ImageFormatter() {
super("image", false);
}
@Override
public void validateArgs(Arguments args) throws ArgumentsException {
args.atMost(1);
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
JsonNode node = var.node();
String cls = (args.count() == 1) ? args.first() : "thumb-image";
String id = node.path("id").asText();
String altText = getAltText(ctx);
String assetUrl = node.path("assetUrl").asText();
String focalPoint = getFocalPoint(node);
String originalSize = node.path("originalSize").asText();
StringBuilder buf = new StringBuilder();
buf.append("");
buf.append("
");
var.set(buf);
}
private String getAltText(Context ctx) {
// For image blocks, caption is stored on the block and not the item.
// need to reach out via the context to see if it exist first,
// before falling back on the data on the item
// this will be empty if this is not a block
JsonNode blockInfo = ctx.resolve("info");
if (blockInfo != null) {
JsonNode altText = blockInfo.get("altText");
if (altText != null && StringUtils.trimToNull(altText.asText()) != null) {
return altText.asText();
}
}
JsonNode image = ctx.node();
return getAltTextFromContentItem(image);
}
}
public static class ImageColorFormatter extends BaseFormatter {
public ImageColorFormatter() {
super("image-color", false);
}
private final List positions = Arrays.asList(
"topLeft", "topRight", "bottomLeft", "bottomRight", "center"
);
private final Set positionSet = new HashSet<>(positions);
@Override
public void validateArgs(Arguments args) throws ArgumentsException {
args.atMost(2);
if (args.count() >= 1) {
String pos = args.first();
if (!positionSet.contains(pos)) {
throw new ArgumentsException("illegal value '" + pos + "' found");
}
}
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
JsonNode colorData = var.node().path("colorData");
if (colorData.isMissingNode()) {
var.setMissing();
return;
}
StringBuilder buf = new StringBuilder();
if (args.count() > 0) {
String key = args.first();
String color = colorData.path(key + "Average").asText();
if (color.length() > 0) {
if (args.count() == 2) {
buf.append(args.get(1)).append(": ");
}
buf.append('#').append(color);
} else {
buf.append("\"").append(key).append("\" not found.");
}
} else {
for (String key : positions) {
buf.append("data-color-").append(key).append("=\"#");
buf.append(colorData.path(key + "Average").asText());
buf.append("\" ");
}
}
var.set(buf);
}
}
public static class ImageMetaFormatter extends ImageMetaBaseFormatter {
public ImageMetaFormatter() {
super("image-meta");
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
StringBuilder buf = new StringBuilder();
outputImageMeta(var.node(), buf);
var.set(buf);
}
}
public static class ImageMetaSrcSetFormatter extends BaseFormatter {
public ImageMetaSrcSetFormatter() {
super("image-srcset", false);
}
protected static int filterVariants(String[] variants) {
int c = 0;
for (int i = 0; i < variants.length; i++) {
if (variants[i].endsWith("w")) {
variants[c] = variants[i];
c++;
}
}
return c;
}
protected void outputImageSrcSet(JsonNode image, StringBuilder buf) {
if (image.isMissingNode()) {
return;
}
String assetUrl = image.path("assetUrl").asText();
String[] variants = image.path("systemDataVariants").asText().split(",");
int limit = filterVariants(variants);
if (limit == 0) {
return;
}
buf.append(" data-srcset=\"");
for (int i = 0; i < limit; i++) {
if (i > 0) {
buf.append(',');
}
buf.append(assetUrl).append("?format=").append(variants[i]).append(" ").append(variants[i]);
}
buf.append("\"");
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
StringBuilder buf = new StringBuilder();
Variable var = variables.first();
outputImageSrcSet(var.node(), buf);
var.set(buf);
}
}
public static class CoverImageMetaFormatter extends ImageMetaBaseFormatter {
public CoverImageMetaFormatter() {
super("cover-image-meta");
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
StringBuilder buf = new StringBuilder();
outputImageMeta(var.node().path("coverImage"), buf);
var.set(buf);
}
};
/**
* ITEM_CLASSES
*/
public static class ItemClassesFormatter extends BaseFormatter {
public ItemClassesFormatter() {
super("item-classes", false);
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
JsonNode value = var.node();
StringBuilder buf = new StringBuilder();
buf.append("hentry");
JsonNode node = ctx.resolve("promotedBlockType");
if (isTruthy(node)) {
buf.append(" promoted promoted-block-" + slugify(node.asText()));
}
node = ctx.resolve("categories");
if (isTruthy(node)) {
int size = node.size();
for (int i = 0; i < size; i++) {
buf.append(" category-" + slugify(node.path(i).asText()));
}
}
node = ctx.resolve("tags");
if (isTruthy(node)) {
int size = node.size();
for (int i = 0; i < size; i++) {
buf.append(" tag-" + slugify(node.path(i).asText()));
}
}
node = ctx.resolve("author");
JsonNode displayName = node.path("displayName");
if (isTruthy(node) && isTruthy(displayName)) {
buf.append(" author-" + slugify(displayName.asText()));
}
node = ctx.resolve("recordTypeLabel");
buf.append(" post-type-").append(node.asText());
node = ctx.resolve("@index");
if (!node.isMissingNode()) {
buf.append(" article-index-" + node.asInt());
}
node = ctx.resolve("starred");
if (isTruthy(node)) {
buf.append(" featured");
}
node = value.path("recordType");
if (RecordType.STORE_ITEM.code() == node.asInt()) {
if (CommerceUtils.isOnSale(value)) {
buf.append(" on-sale");
}
if (CommerceUtils.isSoldOut(value)) {
buf.append(" sold-out");
}
}
var.set(buf);
}
}
private static abstract class ResizeBaseFormatter extends BaseFormatter {
ResizeBaseFormatter(String identifier) {
super(identifier, true);
}
@Override
public void validateArgs(Arguments args) throws ArgumentsException {
args.atLeast(1);
Integer requestedWidth = Integer.parseInt(args.first());
args.setOpaque(requestedWidth);
}
protected JsonNode resize(Context ctx, JsonNode node, boolean resizeWidth, int requested) {
String[] parts = splitDimensions(node);
if (parts == null || parts.length != 2) {
return new TextNode("Invalid source parameter. Pass in 'originalSize'.");
}
int width = Integer.parseInt(parts[0]);
int height = Integer.parseInt(parts[1]);
int value = 0;
if (resizeWidth) {
value = (int)(width * (requested / (float)height));
} else {
value = (int)(height * (requested / (float)width));
}
return new IntNode(value);
}
protected String getSquarespaceSizeForWidth(int width) {
if (width > 1000) {
return "1500w";
} else if (width > 750) {
return "1000w";
} else if (width > 500) {
return "750w";
} else if (width > 300) {
return "500w";
} else if (width > 100) {
return "300w";
} else {
return "100w";
}
}
}
public static class ResizedHeightForWidthFormatter extends ResizeBaseFormatter {
public ResizedHeightForWidthFormatter() {
super("resizedHeightForWidth");
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
var.set(resize(ctx, var.node(), false, (Integer)args.getOpaque()));
}
}
public static class ResizedWidthForHeightFormatter extends ResizeBaseFormatter {
public ResizedWidthForHeightFormatter() {
super("resizedWidthForHeight");
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
var.set(resize(ctx, var.node(), true, (Integer)args.getOpaque()));
}
}
public static class SqspThumbForWidthFormatter extends ResizeBaseFormatter {
public SqspThumbForWidthFormatter() {
super("squarespaceThumbnailForWidth");
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
var.set(getSquarespaceSizeForWidth((Integer)args.getOpaque()));
}
}
public static class SqspThumbForHeightFormatter extends ResizeBaseFormatter {
public SqspThumbForHeightFormatter() {
super("squarespaceThumbnailForHeight");
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
JsonNode resized = resize(ctx, var.node(), true, (Integer)args.getOpaque());
if (resized.isInt()) {
var.set(getSquarespaceSizeForWidth(resized.asInt()));
} else {
var.set(resized);
}
}
}
/**
* TIMESINCE - Outputs a human-readable representation of (now - timestamp).
*/
public static class TimesinceFormatter extends BaseFormatter {
public TimesinceFormatter() {
super("timesince", false);
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
JsonNode node = var.node();
StringBuilder buf = new StringBuilder();
if (!node.isNumber()) {
buf.append("Invalid date.");
} else {
long value = node.asLong();
buf.append("");
PluginDateUtils.humanizeDate(value, false, buf);
buf.append("");
}
var.set(buf);
}
}
public static class WidthFormatter extends BaseFormatter {
public WidthFormatter() {
super("width", false);
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
String[] parts = splitDimensions(var.node());
if (parts == null || parts.length != 2) {
var.set("Invalid source parameter. Pass in 'originalSize'.");
} else {
int width = Integer.parseInt(parts[0]);
var.set(width);
}
}
}
public static class VideoFormatter extends BaseFormatter {
public VideoFormatter() {
super("video", false);
}
private final Set validArgs = new HashSet<>(Arrays.asList("load-false", "color-data"));
@Override
public void validateArgs(Arguments args) throws ArgumentsException {
for (String arg : args.getArgs()) {
if (!validArgs.contains(arg)) {
throw new ArgumentsException("'" + arg + "' is not an expected value");
}
}
}
@Override
public void apply(Context ctx, Arguments args, Variables variables) throws CodeExecuteException {
Variable var = variables.first();
JsonNode node = var.node();
JsonNode oEmbed = node.path("oembed");
JsonNode colorData = node.path("colorData");
String assetUrl = node.path("assetUrl").asText();
String focalPoint = getFocalPoint(node);
String originalSize = node.path("originalSize").asText();
boolean loadFalse = false;
boolean useColorData = false;
for (String arg : args.getArgs()) {
if (arg.equals("load-false")) {
loadFalse = true;
} else if (arg.equals("color-data")) {
useColorData = true;
}
}
StringBuilder buf = new StringBuilder();
buf.append("");
if (isTruthy(node.path("overlay"))) {
buf.append("");
var.set(buf);
}
}
}
");
}
buf.append("
© 2015 - 2025 Weber Informatics LLC | Privacy Policy