Skip to content
This repository has been archived by the owner on Jun 12, 2020. It is now read-only.

Commit

Permalink
Handle script tags (#27)
Browse files Browse the repository at this point in the history
* parse script tags in html head for composition

* introduced asset class for parsed asset links, added handling of js assets

* filter options attribute when rendering asset links into response

* Improved equals methods, added missing test, added code comment
  • Loading branch information
thovid authored and AndreasKl committed Mar 10, 2018
1 parent ff1e7c4 commit e8b73a8
Show file tree
Hide file tree
Showing 14 changed files with 238 additions and 91 deletions.
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Using content tags to mark the content to be included allows the response of som

**Specify assets required for included content**

Content fragments usually need some assets like css and/or javascript to work. For this. content responses can mark asset links using a special data attribute. The name of this attribute is *data-rd-options* and can be changed via configuration. If an asset link in the html head of the content response contains the `data-rd-options` attribute with value `include`, this link is included in the head of the document returned to the caller.
Content fragments usually need some assets like css and/or javascript to work. For this. content responses can mark asset links resp. script includes in their head section using a special data attribute. The name of this attribute is *data-rd-options* and can be changed via configuration. If an asset link/script in the html head of the content response contains the `data-rd-options` attribute with value `include`, this link/script is included in the head of the document returned to the caller.

*Example*
```
Expand Down
117 changes: 117 additions & 0 deletions src/main/java/com/rewedigital/composer/composing/Asset.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.rewedigital.composer.composing;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.function.Predicate;

/**
* Describes a <code>link</code> or <code>script</code> tag found in an html <code>head</code> section during parsing of
* a template or content fragment.
*/
class Asset {
public static class Builder {

private final String optionsAttributeName;
private String type = "link";
private boolean selfClosing = false;
private final Map<String, String> attributes = new HashMap<>();

public Builder(final String optionsAttributeName) {
this.optionsAttributeName = optionsAttributeName;
}

public Asset.Builder type(final String type) {
this.type = type;
return this;
}

public Asset.Builder attribute(final String name, final String value) {
this.attributes.put(name, value);
return this;
}

public Asset.Builder selfClosing(final boolean selfClosing) {
this.selfClosing = selfClosing;
return this;
}

public boolean isInclude() {
return optionsContain("include");
}

private boolean optionsContain(final String option) {
return attributes.getOrDefault(optionsAttributeName, "").contains(option);
}

public Asset build() {
return new Asset(this);
}
}

private final String optionsAttributeName;
private final String type;
private final Map<String, String> attributes;
private final boolean selfClosing;

private Asset(final Asset.Builder builder) {
this.optionsAttributeName = builder.optionsAttributeName;
this.type = builder.type;
this.selfClosing = builder.selfClosing;
this.attributes = new HashMap<>(builder.attributes);
}

public String render() {
return attributes
.entrySet()
.stream()
.filter(notOptionsAttribute())
.reduce(new StringBuilder().append(renderOpen()),
(builder, e) -> builder.append(e.getKey())
.append("=\"")
.append(e.getValue())
.append("\" "),
(a, b) -> a.append(b))
.append(renderClosing()).toString();
}

private Predicate<? super Entry<String, String>> notOptionsAttribute() {
return e -> !e.getKey().equals(optionsAttributeName);
}

@Override
public int hashCode() {
return Objects.hash(attributes, selfClosing, type);
}

@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Asset other = (Asset) obj;
return selfClosing == other.selfClosing &&
Objects.equals(attributes, other.attributes) &&
Objects.equals(type, other.type);
}

@Override
public String toString() {
return "Asset [type=" + type + ", attributes=" + attributes + ", selfClosing=" + selfClosing + "]";
}

private String renderOpen() {
return "<" + type + " ";
}

private String renderClosing() {
if (selfClosing) {
return "/>";
}
return "></" + type + ">";
}
}
39 changes: 19 additions & 20 deletions src/main/java/com/rewedigital/composer/composing/Composition.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,50 +36,47 @@ public interface Extractor<T> {
}

private final List<Composition> children;
private final List<String> assetLinks;
private final List<Asset> assets;
private final int startOffset;
private final int endOffset;
private final String template;
private final ContentRange contentRange;
private final SessionFragment session;

private Composition(final String template, final ContentRange contentRange, final List<String> assetLinks,
private Composition(final String template, final ContentRange contentRange, final List<Asset> assets,
final List<Composition> children) {
this(0, template.length(), template, contentRange, assetLinks, SessionFragment.empty(), children);
this(0, template.length(), template, contentRange, assets, SessionFragment.empty(), children);
}

private Composition(final int startOffset, final int endOffset, final String template,
final ContentRange contentRange, final List<String> assetLinks, final SessionFragment session,
final ContentRange contentRange, final List<Asset> assets,
final SessionFragment session,
final List<Composition> children) {
this.startOffset = startOffset;
this.endOffset = endOffset;
this.template = template;
this.contentRange = contentRange;
this.assetLinks = assetLinks;
this.assets = assets;
this.children = children;
this.session = session;
}

public Composition forRange(final int startOffset, final int endOffset) {
return new Composition(startOffset, endOffset, template, contentRange, assetLinks, session,
return new Composition(startOffset, endOffset, template, contentRange, assets, session,
children);
}

public Composition withSession(final SessionFragment session) {
return new Composition(startOffset, endOffset, template, contentRange, assetLinks,
return new Composition(startOffset, endOffset, template, contentRange, assets,
this.session.mergedWith(session), children);
}

public static Composition of(final String template, final ContentRange contentRange,
final List<String> assetLinks, final List<Composition> children) {
return new Composition(template, contentRange, assetLinks, children);
}

public <R> R map(final BiFunction<String, SessionFragment, R> mapping) {
return mapping.apply(withAssetLinks(body()), mergedSession());
public static Composition of(final String template, final ContentRange contentRange, final List<Asset> assets,
final List<Composition> children) {
return new Composition(template, contentRange, assets, children);
}

public <R> R extract(Extractor<R> extractor) {
public <R> R extract(final Extractor<R> extractor) {
return extractor.extract(withAssetLinks(body()), mergedSession());
}

Expand All @@ -90,7 +87,7 @@ private String body() {
writer.write(template, currentIndex, c.startOffset - currentIndex);
writer.write(c.body());
currentIndex = c.endOffset;
assetLinks.addAll(c.assetLinks);
assets.addAll(c.assets);
}
writer.write(template, currentIndex, contentRange.end() - currentIndex);
return writer.toString();
Expand All @@ -104,9 +101,11 @@ private SessionFragment mergedSession() {
}

private String withAssetLinks(final String body) {
final String assets = assetLinks.stream()
.distinct()
.collect(Collectors.joining("\n"));
return body.replaceFirst("</head>", assets + "\n</head>");
final String renderedAssets =
assets.stream().distinct()
.map(Asset::render)
.collect(Collectors.joining("\n"));

return body.replaceFirst("</head>", renderedAssets + "\n</head>");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ public String callStack() {

return "[" + path + "] included via " + parent.callStack();
}

@Override
public String toString() {
return callStack();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.rewedigital.composer.composing;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.attoparser.AbstractMarkupHandler;
import org.attoparser.ParseException;
Expand All @@ -14,15 +12,15 @@ class ContentMarkupHandler extends AbstractMarkupHandler {
private final char[] contentTag;
private final String assetOptionsAttribute;

private final List<String> links = new LinkedList<>();
private final Map<String, String> attributes = new HashMap<>();
private final List<Asset> assets = new LinkedList<>();

private Asset.Builder current = null;
private final ContentRange defaultContentRange;

private int contentStart = 0;
private int contentEnd = 0;

private boolean parsingHead = false;
private boolean parsingLink = false;


public ContentMarkupHandler(final ContentRange defaultContentRange, final ComposerHtmlConfiguration configuration) {
Expand All @@ -35,25 +33,26 @@ public ContentRange contentRange() {
return contentEnd <= 0 ? defaultContentRange : new ContentRange(contentStart, contentEnd);
}

public List<String> assetLinks() {
return links;
public List<Asset> assets() {
return assets;
}

@Override
public void handleStandaloneElementStart(final char[] buffer, final int nameOffset, final int nameLen,
final boolean minimized, final int line, final int col) throws ParseException {
super.handleStandaloneElementStart(buffer, nameOffset, nameLen, minimized, line, col);
if (parsingHead && isLinkElement(buffer, nameOffset, nameLen)) {
startLink();
if (parsingHead && isAssetElement(buffer, nameOffset, nameLen)) {
startAsset(buffer, nameOffset, nameLen, true);
}
}

@Override
public void handleStandaloneElementEnd(char[] buffer, int nameOffset, int nameLen, boolean minimized, int line,
int col) throws ParseException {
public void handleStandaloneElementEnd(final char[] buffer, final int nameOffset, final int nameLen,
final boolean minimized, final int line,
final int col) throws ParseException {
super.handleStandaloneElementEnd(buffer, nameOffset, nameLen, minimized, line, col);
if (parsingLink) {
pushLink();
if (parsingAsset()) {
pushAsset();
}
}

Expand All @@ -66,6 +65,8 @@ public void handleOpenElementStart(final char[] buffer, final int nameOffset, fi
parsingHead = true;
} else if (isContentElement(buffer, nameOffset, nameLen)) {
contentStart = nameOffset + nameLen + 1;
} else if (parsingHead && isAssetElement(buffer, nameOffset, nameLen)) {
startAsset(buffer, nameOffset, nameLen, false);
}
}

Expand All @@ -77,29 +78,42 @@ public void handleCloseElementEnd(final char[] buffer, final int nameOffset, fin
parsingHead = false;
} else if (isContentElement(buffer, nameOffset, nameLen) && contentStart >= 0) {
contentEnd = nameOffset - 2;
} else if (parsingAsset()) {
pushAsset();
}
}

@Override
public void handleAttribute(char[] buffer, int nameOffset, int nameLen, int nameLine, int nameCol,
int operatorOffset, int operatorLen, int operatorLine, int operatorCol, int valueContentOffset,
int valueContentLen, int valueOuterOffset, int valueOuterLen, int valueLine, int valueCol)
public void handleAttribute(final char[] buffer, final int nameOffset, final int nameLen, final int nameLine,
final int nameCol,
final int operatorOffset, final int operatorLen, final int operatorLine, final int operatorCol,
final int valueContentOffset,
final int valueContentLen, final int valueOuterOffset, final int valueOuterLen, final int valueLine,
final int valueCol)
throws ParseException {
super.handleAttribute(buffer, nameOffset, nameLen, nameLine, nameCol, operatorOffset, operatorLen, operatorLine,
operatorCol, valueContentOffset, valueContentLen, valueOuterOffset, valueOuterLen, valueLine, valueCol);

if (parsingLink) {
attributes.put(new String(buffer, nameOffset, nameLen),
if (parsingAsset()) {
current = current.attribute(new String(buffer, nameOffset, nameLen),
new String(buffer, valueContentOffset, valueContentLen));
}
}

private boolean isLinkElement(final char[] buffer, final int nameOffset, final int nameLen) {
return TextUtil.contains(true, buffer, nameOffset, nameLen, "link", 0, "link".length());
private boolean parsingAsset() {
return current != null;
}

private boolean isAssetElement(final char[] buffer, final int nameOffset, final int nameLen) {
return textContains(buffer, nameOffset, nameLen, "link") || textContains(buffer, nameOffset, nameLen, "script");
}

private boolean isHeadElement(final char[] buffer, final int nameOffset, final int nameLen) {
return TextUtil.contains(true, buffer, nameOffset, nameLen, "head", 0, "head".length());
return textContains(buffer, nameOffset, nameLen, "head");
}

private boolean textContains(final char[] buffer, final int nameOffset, final int nameLen, final String item) {
return TextUtil.contains(true, buffer, nameOffset, nameLen, item, 0, item.length());
}

private boolean isContentElement(final char[] buffer, final int nameOffset, final int nameLen) {
Expand All @@ -108,26 +122,17 @@ private boolean isContentElement(final char[] buffer, final int nameOffset, fina
}


private void startLink() {
parsingLink = true;
private void startAsset(final char[] buffer, final int nameOffset, final int nameLen, final boolean selfClosing) {
current = new Asset.Builder(assetOptionsAttribute)
.type(new String(buffer, nameOffset, nameLen))
.selfClosing(selfClosing);
}

private void pushLink() {
if (attributes.getOrDefault(assetOptionsAttribute, "").contains("include")) {
links.add(
attributes
.entrySet()
.stream()
.reduce(new StringBuilder("<link "),
(builder, e) -> builder.append(e.getKey())
.append("=\"")
.append(e.getValue())
.append("\" "),
(a, b) -> a.append(b))
.append("/>").toString());
private void pushAsset() {
if (current.isInclude()) {
assets.add(current.build());
}
parsingLink = false;
attributes.clear();
current = null;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public boolean equals(Object obj) {
if (getClass() != obj.getClass())
return false;
ContentRange other = (ContentRange) obj;
return Objects.equals(this.end, other.end) && Objects.equals(this.start, other.start);
return this.end == other.end && this.start == other.start;
}

@Override
Expand Down
Loading

0 comments on commit e8b73a8

Please sign in to comment.