aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/zwobble/mammoth/internal/html
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2016-02-24 08:16:16 +0000
committerMichael Williamson <mike@zwobble.org>2016-02-24 08:16:16 +0000
commit3e2786a004eddf968a5fa7d5a92dff5b1b5f7734 (patch)
tree7fa1bcc1e484cd0ef782f64baf504ee83333d397 /src/main/java/org/zwobble/mammoth/internal/html
parent0dc3a12affc2e46c1a0df2899cf51a1ae3fdbd61 (diff)
Create internal package
Diffstat (limited to 'src/main/java/org/zwobble/mammoth/internal/html')
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/html/Html.java179
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/html/HtmlElement.java48
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/html/HtmlForceWrite.java18
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/html/HtmlNode.java21
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/html/HtmlSelfClosingElement.java31
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/html/HtmlTextNode.java23
-rw-r--r--src/main/java/org/zwobble/mammoth/internal/html/HtmlWriter.java62
7 files changed, 382 insertions, 0 deletions
diff --git a/src/main/java/org/zwobble/mammoth/internal/html/Html.java b/src/main/java/org/zwobble/mammoth/internal/html/Html.java
new file mode 100644
index 0000000..12ac11e
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/html/Html.java
@@ -0,0 +1,179 @@
+package org.zwobble.mammoth.internal.html;
+
+import org.zwobble.mammoth.internal.util.MammothLists;
+import org.zwobble.mammoth.internal.util.MammothOptionals;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.zwobble.mammoth.internal.util.Casts.tryCast;
+import static org.zwobble.mammoth.internal.util.MammothIterables.tryGetLast;
+import static org.zwobble.mammoth.internal.util.MammothLists.list;
+import static org.zwobble.mammoth.internal.util.MammothMaps.map;
+
+public class Html {
+ public static final HtmlNode FORCE_WRITE = HtmlForceWrite.FORCE_WRITE;
+
+ public static String write(List<HtmlNode> nodes) {
+ StringBuilder builder = new StringBuilder();
+ nodes.forEach(node -> HtmlWriter.write(node, builder));
+ return builder.toString();
+ }
+
+ public static HtmlNode text(String value) {
+ return new HtmlTextNode(value);
+ }
+
+ public static HtmlNode element(String tagName) {
+ return element(tagName, list());
+ }
+
+ public static HtmlNode element(String tagName, Map<String, String> attributes) {
+ return element(tagName, attributes, list());
+ }
+
+ public static HtmlNode element(String tagName, List<HtmlNode> children) {
+ return element(tagName, map(), children);
+ }
+
+ public static HtmlNode element(String tagName, Map<String, String> attributes, List<HtmlNode> children) {
+ return new HtmlElement(list(tagName), attributes, children, false);
+ }
+
+ public static HtmlNode collapsibleElement(String tagName) {
+ return collapsibleElement(list(tagName));
+ }
+
+ public static HtmlNode collapsibleElement(List<String> tagNames) {
+ return collapsibleElement(tagNames, map(), list());
+ }
+
+ public static HtmlNode collapsibleElement(String tagName, List<HtmlNode> children) {
+ return collapsibleElement(tagName, map(), children);
+ }
+
+ public static HtmlNode collapsibleElement(String tagName, Map<String, String> attributes, List<HtmlNode> children) {
+ return collapsibleElement(list(tagName), attributes, children);
+ }
+
+ public static HtmlNode collapsibleElement(List<String> tagNames, Map<String, String> attributes, List<HtmlNode> children) {
+ return new HtmlElement(tagNames, attributes, children, true);
+ }
+
+ public static HtmlNode selfClosingElement(String tagName) {
+ return selfClosingElement(tagName, map());
+ }
+
+ public static HtmlNode selfClosingElement(String tagName, Map<String, String> attributes) {
+ return new HtmlSelfClosingElement(tagName, attributes);
+ }
+
+ public static List<HtmlNode> stripEmpty(List<HtmlNode> nodes) {
+ return MammothLists.eagerFlatMap(nodes, node -> stripEmpty(node));
+ }
+
+ private static List<HtmlNode> stripEmpty(HtmlNode node) {
+ return node.accept(new HtmlNode.Mapper<List<HtmlNode>>() {
+ @Override
+ public List<HtmlNode> visit(HtmlElement element) {
+ List<HtmlNode> children = stripEmpty(element.getChildren());
+ if (children.isEmpty()) {
+ return list();
+ } else {
+ return list(new HtmlElement(
+ element.getTagNames(),
+ element.getAttributes(),
+ children,
+ element.isCollapsible()));
+ }
+ }
+
+ @Override
+ public List<HtmlNode> visit(HtmlSelfClosingElement element) {
+ return list(element);
+ }
+
+ @Override
+ public List<HtmlNode> visit(HtmlTextNode node) {
+ if (node.getValue().isEmpty()) {
+ return list();
+ } else {
+ return list(node);
+ }
+ }
+
+ @Override
+ public List<HtmlNode> visit(HtmlForceWrite forceWrite) {
+ return list(forceWrite);
+ }
+ });
+ }
+
+ public static List<HtmlNode> collapse(List<HtmlNode> nodes) {
+ List<HtmlNode> collapsed = new ArrayList<>();
+
+ for (HtmlNode node : nodes) {
+ collapsingAdd(collapsed, node);
+ }
+
+ return collapsed;
+ }
+
+ private static void collapsingAdd(List<HtmlNode> collapsed, HtmlNode node) {
+ HtmlNode collapsedNode = collapse(node);
+ if (!tryCollapse(collapsed, collapsedNode)) {
+ collapsed.add(collapsedNode);
+ }
+ }
+
+ private static HtmlNode collapse(HtmlNode node) {
+ return node.accept(new HtmlNode.Mapper<HtmlNode>() {
+ @Override
+ public HtmlNode visit(HtmlElement element) {
+ return new HtmlElement(
+ element.getTagNames(),
+ element.getAttributes(),
+ collapse(element.getChildren()),
+ element.isCollapsible());
+ }
+
+ @Override
+ public HtmlNode visit(HtmlSelfClosingElement element) {
+ return element;
+ }
+
+ @Override
+ public HtmlNode visit(HtmlTextNode node) {
+ return node;
+ }
+
+ @Override
+ public HtmlNode visit(HtmlForceWrite forceWrite) {
+ return forceWrite;
+ }
+ });
+ }
+
+ private static boolean tryCollapse(List<HtmlNode> collapsed, HtmlNode node) {
+ return MammothOptionals.map(
+ tryGetLast(collapsed).flatMap(last -> tryCast(HtmlElement.class, last)),
+ tryCast(HtmlElement.class, node),
+ (last, next) -> {
+ if (next.isCollapsible() && isMatch(last, next)) {
+ for (HtmlNode child : next.getChildren()) {
+ collapsingAdd(last.getChildren(), child);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+ ).orElse(false);
+ }
+
+ private static boolean isMatch(HtmlElement first, HtmlElement second) {
+ return second.getTagNames().contains(first.getTagName()) &&
+ first.getAttributes().equals(second.getAttributes());
+ }
+}
diff --git a/src/main/java/org/zwobble/mammoth/internal/html/HtmlElement.java b/src/main/java/org/zwobble/mammoth/internal/html/HtmlElement.java
new file mode 100644
index 0000000..c95f40e
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/html/HtmlElement.java
@@ -0,0 +1,48 @@
+package org.zwobble.mammoth.internal.html;
+
+import java.util.List;
+import java.util.Map;
+
+public class HtmlElement implements HtmlNode {
+ private final List<String> tagNames;
+ private final Map<String, String> attributes;
+ private final List<HtmlNode> children;
+ private final boolean collapsible;
+
+ public HtmlElement(List<String> tagNames, Map<String, String> attributes, List<HtmlNode> children, boolean collapsible) {
+ this.tagNames = tagNames;
+ this.attributes = attributes;
+ this.children = children;
+ this.collapsible = collapsible;
+ }
+
+ public List<String> getTagNames() {
+ return tagNames;
+ }
+
+ public String getTagName() {
+ return tagNames.get(0);
+ }
+
+ public Map<String, String> getAttributes() {
+ return attributes;
+ }
+
+ public List<HtmlNode> getChildren() {
+ return children;
+ }
+
+ public boolean isCollapsible() {
+ return collapsible;
+ }
+
+ @Override
+ public void visit(Visitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ public <T> T accept(Mapper<T> visitor) {
+ return visitor.visit(this);
+ }
+}
diff --git a/src/main/java/org/zwobble/mammoth/internal/html/HtmlForceWrite.java b/src/main/java/org/zwobble/mammoth/internal/html/HtmlForceWrite.java
new file mode 100644
index 0000000..4b3d4bc
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/html/HtmlForceWrite.java
@@ -0,0 +1,18 @@
+package org.zwobble.mammoth.internal.html;
+
+public class HtmlForceWrite implements HtmlNode {
+ public static final HtmlForceWrite FORCE_WRITE = new HtmlForceWrite();
+
+ private HtmlForceWrite() {
+ }
+
+ @Override
+ public void visit(Visitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ public <T> T accept(Mapper<T> visitor) {
+ return visitor.visit(this);
+ }
+}
diff --git a/src/main/java/org/zwobble/mammoth/internal/html/HtmlNode.java b/src/main/java/org/zwobble/mammoth/internal/html/HtmlNode.java
new file mode 100644
index 0000000..a04b57f
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/html/HtmlNode.java
@@ -0,0 +1,21 @@
+package org.zwobble.mammoth.internal.html;
+
+public interface HtmlNode {
+ // TODO: rename visit to accept
+ void visit(Visitor visitor);
+ <T> T accept(Mapper<T> visitor);
+
+ interface Visitor {
+ void visit(HtmlElement element);
+ void visit(HtmlSelfClosingElement element);
+ void visit(HtmlTextNode node);
+ void visit(HtmlForceWrite forceWrite);
+ }
+
+ interface Mapper<T> {
+ T visit(HtmlElement element);
+ T visit(HtmlSelfClosingElement element);
+ T visit(HtmlTextNode node);
+ T visit(HtmlForceWrite forceWrite);
+ }
+}
diff --git a/src/main/java/org/zwobble/mammoth/internal/html/HtmlSelfClosingElement.java b/src/main/java/org/zwobble/mammoth/internal/html/HtmlSelfClosingElement.java
new file mode 100644
index 0000000..adff96f
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/html/HtmlSelfClosingElement.java
@@ -0,0 +1,31 @@
+package org.zwobble.mammoth.internal.html;
+
+import java.util.Map;
+
+public class HtmlSelfClosingElement implements HtmlNode {
+ private final String tagName;
+ private final Map<String, String> attributes;
+
+ public HtmlSelfClosingElement(String tagName, Map<String, String> attributes) {
+ this.tagName = tagName;
+ this.attributes = attributes;
+ }
+
+ public String getTagName() {
+ return tagName;
+ }
+
+ public Map<String, String> getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ public void visit(Visitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ public <T> T accept(Mapper<T> visitor) {
+ return visitor.visit(this);
+ }
+}
diff --git a/src/main/java/org/zwobble/mammoth/internal/html/HtmlTextNode.java b/src/main/java/org/zwobble/mammoth/internal/html/HtmlTextNode.java
new file mode 100644
index 0000000..c26bd33
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/html/HtmlTextNode.java
@@ -0,0 +1,23 @@
+package org.zwobble.mammoth.internal.html;
+
+public class HtmlTextNode implements HtmlNode {
+ private final String value;
+
+ public HtmlTextNode(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public void visit(Visitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ public <T> T accept(Mapper<T> visitor) {
+ return visitor.visit(this);
+ }
+}
diff --git a/src/main/java/org/zwobble/mammoth/internal/html/HtmlWriter.java b/src/main/java/org/zwobble/mammoth/internal/html/HtmlWriter.java
new file mode 100644
index 0000000..164eefc
--- /dev/null
+++ b/src/main/java/org/zwobble/mammoth/internal/html/HtmlWriter.java
@@ -0,0 +1,62 @@
+package org.zwobble.mammoth.internal.html;
+
+import java.util.Map;
+
+import static org.zwobble.mammoth.internal.util.MammothLists.orderedBy;
+
+public class HtmlWriter {
+ public static void write(HtmlNode node, StringBuilder builder) {
+ node.visit(new HtmlNode.Visitor() {
+ @Override
+ public void visit(HtmlElement element) {
+ builder.append("<").append(element.getTagName());
+
+ HtmlWriter.generateAttributes(element.getAttributes(), builder);
+
+ builder.append(">");
+
+ element.getChildren().forEach(node -> write(node, builder));
+
+ builder
+ .append("</")
+ .append(element.getTagName())
+ .append(">");
+ }
+
+ @Override
+ public void visit(HtmlSelfClosingElement element) {
+ builder.append("<").append(element.getTagName());
+ HtmlWriter.generateAttributes(element.getAttributes(), builder);
+ builder.append(" />");
+ }
+
+ @Override
+ public void visit(HtmlTextNode node) {
+ builder.append(HtmlWriter.escapeText(node.getValue()));
+ }
+
+ @Override
+ public void visit(HtmlForceWrite forceWrite) {
+ }
+ });
+ }
+
+ private static void generateAttributes(Map<String, String> attributes, StringBuilder builder) {
+ for (Map.Entry<String, String> attribute : orderedBy(attributes.entrySet(), Map.Entry::getKey)) {
+ builder
+ .append(" ")
+ .append(attribute.getKey())
+ .append("=\"")
+ .append(HtmlWriter.escapeAttributeValue(attribute.getValue()))
+ .append("\"");
+ }
+ }
+
+ private static String escapeText(String text) {
+ return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
+ }
+
+ private static String escapeAttributeValue(String value) {
+ return escapeText(value).replace("\"", "&quot;");
+ }
+}