21 Commits

Author SHA1 Message Date
David
897c53d830 [maven-release-plugin] prepare release j2html-1.2.0 2017-09-03 11:15:46 +02:00
David
1e29d81107 Fix another javadoc violation 2017-09-03 11:14:41 +02:00
David
dee3efb8e7 Remove javadoc violation 2017-09-03 11:08:36 +02:00
David
ee82201dfb Misc cleanup
- Add javadoc to Config
- Make CSSMin/JSMin configurable (add Minifier interface)
- Change SimpleEscaper back to a util
2017-09-02 19:41:37 +02:00
David
691680ea51 Add simple formatter (fixed #65)
* Add simple formatter
* Create Indenter interface
* Shorten variable-names in renderFormatted
* Fix javadoc
2017-09-02 19:05:48 +02:00
David
0853692e80 Use diamond operator 2017-09-01 20:50:20 +02:00
David
c2bc4b3209 Add @FunctionalInterface annotation to TextEscaper 2017-09-01 20:40:57 +02:00
David
ff9d138f63 Add option to close empty tags (fixes #52) 2017-09-01 20:39:23 +02:00
David
f778f4df3c Add convenience method for title (fixes #71) 2017-09-01 20:12:47 +02:00
David
a0b060ac42 Let attr() take object value (fixes #57) 2017-09-01 20:09:10 +02:00
David
4161be67bb Fix escaper-test 2017-09-01 20:07:48 +02:00
David
863527f684 Update README.md 2017-08-14 17:58:37 +02:00
David
5ef2dd3ec5 [maven-release-plugin] prepare for next development iteration 2017-08-14 17:45:23 +02:00
David
fc3bb29b6a [maven-release-plugin] prepare release j2html-1.1.0 2017-08-14 17:45:02 +02:00
Sergey Bezkostnyi
ef5777e54a Add ability to change text escaper implementation (#70) 2017-08-14 17:10:02 +02:00
David
cfc1489399 Merge pull request #63 from maztan/master
Add method to get number of children of ContainerTag
2017-07-29 19:19:08 +02:00
Ed Savailonei
8112dad1bb Add methd to get number of children of ContainerTag 2017-07-03 12:41:10 +02:00
David
235dec78ed Merge pull request #62 from kiru/patch-1
Updaate Gradle Version
2017-05-19 23:05:12 +02:00
Kiru
6316dd2262 Updaate Gradle Version 2017-05-19 21:57:04 +02:00
David
5667d7c68d Update README.md 2017-05-16 18:49:27 +02:00
David
13911d5f57 [maven-release-plugin] prepare for next development iteration 2017-05-16 18:00:33 +02:00
20 changed files with 285 additions and 61 deletions

View File

@@ -13,12 +13,12 @@ The project webpage is [j2html.com](http://j2html.com).
<dependency>
<groupId>com.j2html</groupId>
<artifactId>j2html</artifactId>
<version>0.99</version>
<version>1.1.0</version>
</dependency>
```
### OR the gradle dependency
```
compile 'com.j2html:j2html:0.99'
compile 'com.j2html:j2html:1.1.0'
```
### Import TagCreator and start building HTML

View File

@@ -10,7 +10,7 @@
<groupId>com.j2html</groupId>
<artifactId>j2html</artifactId>
<version>1.0.0</version>
<version>1.2.0</version>
<name>j2html</name>
<description>Java to HTML builder with a fluent API</description>

View File

@@ -0,0 +1,49 @@
package j2html;
import java.util.Collections;
import j2html.utils.CSSMin;
import j2html.utils.EscapeUtil;
import j2html.utils.Indenter;
import j2html.utils.JSMin;
import j2html.utils.Minifier;
import j2html.utils.TextEscaper;
public class Config {
private Config() {
}
/**
* Change this to configure text-escaping
* For example, to disable escaping, do <code>{@code Config.textEscaper = text -> text;}</code>
*/
public static TextEscaper textEscaper = EscapeUtil::escape;
/**
* Change this to configure css-minification.
* The default minifier is https://github.com/barryvan/CSSMin
*/
public static Minifier cssMinifier = CSSMin::compressCss;
/**
* Change this to configure js-minification.
* The default minifier is a simple whitespace/newline stripper
*/
public static Minifier jsMinifier = JSMin::compressJs;
/**
* Change this to configure indentation when rendering formatted html
* The default is four spaces
*/
private static String FOUR_SPACES = " ";
public static Indenter indenter = (level, text) -> String.join("", Collections.nCopies(level, FOUR_SPACES)) + text;
/**
* Change this to configure enable/disable closing empty tags
* The default is to NOT close them
*/
public static boolean closeEmptyTags = false;
}

View File

@@ -1,7 +1,7 @@
package j2html.attributes;
import j2html.utils.SimpleEscaper;
import j2html.Config;
public class Attribute {
private String name;
@@ -9,7 +9,7 @@ public class Attribute {
public Attribute(String name, String value) {
this.name = name;
this.value = SimpleEscaper.escape(value);
this.value = Config.textEscaper.escape(value);
}
public Attribute(String name) {

View File

@@ -4,13 +4,15 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import j2html.Config;
public class ContainerTag extends Tag<ContainerTag> {
private List<DomContent> children;
public ContainerTag(String tagName) {
super(tagName);
this.children = new ArrayList<DomContent>();
this.children = new ArrayList<>();
}
@@ -111,6 +113,12 @@ public class ContainerTag extends Tag<ContainerTag> {
return with(new Text(text));
}
/**
* Gets number of child nodes this tag element contains
*/
public int getNumChildren() {
return children.size();
}
/**
* Render the ContainerTag and its children
@@ -120,7 +128,7 @@ public class ContainerTag extends Tag<ContainerTag> {
@Override
public String render() {
StringBuilder rendered = new StringBuilder(renderOpenTag());
if (children != null && !children.isEmpty()) {
if (!children.isEmpty()) {
for (DomContent child : children) {
rendered.append(child.render());
}
@@ -129,6 +137,34 @@ public class ContainerTag extends Tag<ContainerTag> {
return rendered.toString();
}
/**
* Render the ContainerTag and its children, adding newlines before each
* child and using Config.indenter to indent child based on how deep
* in the tree it is
*
* @return the rendered and formatted string
*/
public String renderFormatted() {
return renderFormatted(0);
}
private String renderFormatted(int lvl) {
StringBuilder sb = new StringBuilder(renderOpenTag() + "\n");
if (!children.isEmpty()) {
for (DomContent c : children) {
lvl++;
if (c instanceof ContainerTag) {
sb.append(Config.indenter.indent(lvl, ((ContainerTag) c).renderFormatted(lvl)));
} else {
sb.append(Config.indenter.indent(lvl, c.render())).append("\n");
}
lvl--;
}
}
sb.append(Config.indenter.indent(lvl, renderCloseTag())).append("\n");
return sb.toString();
}
@Override
public void render(Appendable writer) throws IOException {
writer.append(renderOpenTag());

View File

@@ -1,5 +1,7 @@
package j2html.tags;
import j2html.Config;
public class EmptyTag extends Tag<EmptyTag> {
public EmptyTag(String tagName) {
@@ -8,6 +10,10 @@ public class EmptyTag extends Tag<EmptyTag> {
@Override
public String render() {
if (Config.closeEmptyTags) {
String tag = renderOpenTag();
return tag.substring(0, tag.length() - 1) + "/>";
}
return renderOpenTag();
}

View File

@@ -4,6 +4,7 @@ import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import j2html.Config;
import j2html.utils.CSSMin;
import j2html.utils.JSMin;
@@ -16,8 +17,8 @@ public class InlineStaticResource {
public static ContainerTag get(String path, TargetFormat format) {
String fileString = getFileAsString(path);
switch (format) {
case CSS_MIN : return style().with(rawHtml(CSSMin.compress(fileString)));
case JS_MIN : return script().with(rawHtml(JSMin.compressJs(fileString)));
case CSS_MIN : return style().with(rawHtml(Config.cssMinifier.minify(fileString)));
case JS_MIN : return script().with(rawHtml(Config.jsMinifier.minify((fileString))));
case CSS : return style().with(rawHtml(fileString));
case JS : return script().with(rawHtml(fileString));
default : throw new RuntimeException("Invalid target format");

View File

@@ -1,8 +1,6 @@
package j2html.tags;
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import j2html.attributes.Attr;
import j2html.attributes.Attribute;
@@ -14,7 +12,7 @@ public abstract class Tag<T extends Tag<T>> extends DomContent {
protected Tag(String tagName) {
this.tagName = tagName;
this.attributes = new ArrayList<Attribute>();
this.attributes = new ArrayList<>();
}
public String getTagName() {
@@ -63,8 +61,8 @@ public abstract class Tag<T extends Tag<T>> extends DomContent {
* @param value the attribute value
* @return itself for easy chaining
*/
public T attr(String attribute, String value) {
setAttribute(attribute, value);
public T attr(String attribute, Object value) {
setAttribute(attribute, value == null ? null : String.valueOf(value));
return (T) this;
}
@@ -73,7 +71,7 @@ public abstract class Tag<T extends Tag<T>> extends DomContent {
*
* @param attribute the attribute name
* @return itself for easy chaining
* @see Tag#attr(String, String)
* @see Tag#attr(String, Object)
*/
public T attr(String attribute) {
return attr(attribute, null);
@@ -82,7 +80,7 @@ public abstract class Tag<T extends Tag<T>> extends DomContent {
/**
* Call attr-method based on condition
* {@link #attr(String attribute, String value)}
* {@link #attr(String attribute, Object value)}
*
* @param condition the condition
* @param attribute the attribute name
@@ -105,6 +103,7 @@ public abstract class Tag<T extends Tag<T>> extends DomContent {
/**
* Convenience methods that call attr with predefined attributes
*
* @return itself for easy chaining
*/
@@ -132,6 +131,7 @@ public abstract class Tag<T extends Tag<T>> extends DomContent {
public T withName(String name) { return attr(Attr.NAME, name); }
public T withPlaceholder(String placeholder) { return attr(Attr.PLACEHOLDER, placeholder); }
public T withTarget(String target) { return attr(Attr.TARGET, target); }
public T withTitle(String title) { return attr(Attr.TITLE, title); }
public T withType(String type) { return attr(Attr.TYPE, type); }
public T withRel(String rel) { return attr(Attr.REL, rel); }
public T withRole(String role) { return attr(Attr.ROLE, role); }
@@ -155,6 +155,7 @@ public abstract class Tag<T extends Tag<T>> extends DomContent {
public T withCondName(boolean condition, String name) { return condAttr(condition, Attr.NAME, name); }
public T withCondPlaceholder(boolean condition, String placeholder) { return condAttr(condition, Attr.PLACEHOLDER, placeholder); }
public T withCondTarget(boolean condition, String target) { return condAttr(condition, Attr.TARGET, target); }
public T withCondTitle(boolean condition, String title) { return condAttr(condition, Attr.TITLE, title); }
public T withCondType(boolean condition, String type) { return condAttr(condition, Attr.TYPE, type); }
public T withCondRel(boolean condition, String rel) { return condAttr(condition, Attr.REL, rel); }
public T withCondSrc(boolean condition, String src) { return condAttr(condition, Attr.SRC, src); }

View File

@@ -1,6 +1,6 @@
package j2html.tags;
import j2html.utils.SimpleEscaper;
import j2html.Config;
public class Text extends DomContent {
@@ -12,7 +12,7 @@ public class Text extends DomContent {
@Override
public String render() {
return SimpleEscaper.escape(text);
return Config.textEscaper.escape(text);
}
}

View File

@@ -1,4 +1,4 @@
/**
/*
* * CSSMin Copyright License Agreement (BSD License)
* <p>
* Copyright (c) 2011-2014, Barry van Oudtshoorn
@@ -79,11 +79,11 @@ public class CSSMin {
* @param input the CSS
* @return the compressed version
*/
public static String compress(String input) {
public static String compressCss(String input) {
try {
int k,
j, // Number of open braces
n; // Current position in stream
j, // Number of open braces
n; // Current position in stream
char curr;
BufferedReader br = new BufferedReader(new StringReader(input));
@@ -124,7 +124,7 @@ public class CSSMin {
if (debugLogging) {
LOG.info("Parsing and processing selectors...");
}
Vector<Selector> selectors = new Vector<Selector>();
Vector<Selector> selectors = new Vector<>();
n = 0;
j = 0;
for (int i = 0; i < sb.length(); i++) {
@@ -203,7 +203,7 @@ class Selector {
// We're dealing with a nested property, eg @-webkit-keyframes
if (parts.length > 2) {
this.subSelectors = new Vector<Selector>();
this.subSelectors = new Vector<>();
parts = selector.split("(\\s*\\{\\s*)|(\\s*\\}\\s*)");
for (int i = 1; i < parts.length; i += 2) {
parts[i] = parts[i].trim();
@@ -264,9 +264,9 @@ class Selector {
* @return An array of properties parsed from this selector.
*/
private ArrayList<Property> parseProperties(String contents) {
ArrayList<String> parts = new ArrayList<String>();
ArrayList<String> parts = new ArrayList<>();
boolean bInsideString = false,
bInsideURL = false;
bInsideURL = false;
int j = 0;
String substr;
for (int i = 0; i < contents.length(); i++) {
@@ -293,7 +293,7 @@ class Selector {
parts.add(substr);
}
ArrayList<Property> results = new ArrayList<Property>();
ArrayList<Property> results = new ArrayList<>();
for (String part : parts) {
try {
results.add(new Property(part));
@@ -331,7 +331,7 @@ class Property implements Comparable<Property> {
Property(String property) throws IncompletePropertyException {
try {
// Parse the property.
ArrayList<String> parts = new ArrayList<String>();
ArrayList<String> parts = new ArrayList<>();
boolean bCanSplit = true;
int j = 0;
String substr;

View File

@@ -1,6 +1,6 @@
package j2html.utils;
public class SimpleEscaper {
public class EscapeUtil {
public static String escape(String s) {
if (s == null) {

View File

@@ -0,0 +1,6 @@
package j2html.utils;
@FunctionalInterface
public interface Indenter {
String indent(int level, String text);
}

View File

@@ -302,4 +302,4 @@ public class JSMin {
private class UnterminatedRegExpLiteralException extends Exception {
}
}
}

View File

@@ -0,0 +1,6 @@
package j2html.utils;
@FunctionalInterface
public interface Minifier {
String minify(String s);
}

View File

@@ -0,0 +1,6 @@
package j2html.utils;
@FunctionalInterface
public interface TextEscaper {
String escape(String text);
}

View File

@@ -5,7 +5,7 @@ import java.util.concurrent.Callable;
import org.apache.commons.lang3.StringEscapeUtils;
import org.junit.Test;
import j2html.utils.SimpleEscaper;
import j2html.utils.EscapeUtil;
public class PerformanceTest {
@@ -30,8 +30,8 @@ public class PerformanceTest {
@Test
public void test_escaper_performnce() throws Exception {
timeEscaper("SimpleEscaper#short", () -> SimpleEscaper.escape(shortTestString));
timeEscaper("SimpleEscaper#long", () -> SimpleEscaper.escape(longTestString));
timeEscaper("SimpleEscaper#short", () -> EscapeUtil.escape(shortTestString));
timeEscaper("SimpleEscaper#long", () -> EscapeUtil.escape(longTestString));
timeEscaper("ApacheEscaper#short", () -> StringEscapeUtils.escapeHtml4(shortTestString));
timeEscaper("ApacheEscaper#long", () -> StringEscapeUtils.escapeHtml4(longTestString));
}

View File

@@ -0,0 +1,38 @@
package j2html;
import org.junit.Test;
import j2html.utils.EscapeUtil;
import j2html.utils.TextEscaper;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
public class TextEscaperTest {
@Test
public void testTextEscaper() throws Exception {
String expected = "&lt;div&gt;&lt;/div&gt;";
assertThat("default text escaper works",
Config.textEscaper.escape("<div></div>"), is(expected));
Config.textEscaper = new NoOpEscaper();
assertThat("user can change text escaper implementation",
Config.textEscaper, is(instanceOf(NoOpEscaper.class)));
expected = "<div></div>";
assertThat("user provided text escaper actually works",
Config.textEscaper.escape("<div></div>"), is(expected));
Config.textEscaper = EscapeUtil::escape; // reset escaper
}
private static class NoOpEscaper implements TextEscaper {
@Override
public String escape(String text) {
return text;
}
}
}

View File

@@ -2,25 +2,10 @@ package j2html.tags;
import org.junit.Test;
import j2html.TagCreator;
import j2html.attributes.Attr;
import static j2html.TagCreator.a;
import static j2html.TagCreator.body;
import static j2html.TagCreator.button;
import static j2html.TagCreator.div;
import static j2html.TagCreator.document;
import static j2html.TagCreator.footer;
import static j2html.TagCreator.h1;
import static j2html.TagCreator.h2;
import static j2html.TagCreator.head;
import static j2html.TagCreator.header;
import static j2html.TagCreator.html;
import static j2html.TagCreator.iff;
import static j2html.TagCreator.input;
import static j2html.TagCreator.main;
import static j2html.TagCreator.script;
import static j2html.TagCreator.text;
import static j2html.TagCreator.title;
import static j2html.TagCreator.*;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -87,10 +72,82 @@ public class ComplexRenderTest {
).render();
}
private String renderTest3() {
boolean USER_SHOULD_LOG_IN = true;
boolean USER_SHOULD_SIGN_UP = false;
return document().render() + "\n" +
html(
head(
title("Test")
),
body(
header(
h1(
text("Test Header "),
a("with link").withHref("http://example.com"),
text(".")
)
),
main(
h2("Test Form"),
div(
input().withType("email").withName("email").withPlaceholder("Email"),
input().withType("password").withName("password").withPlaceholder("Password"),
iff(USER_SHOULD_LOG_IN, button().withType("submit").withText("Login")),
iff(USER_SHOULD_SIGN_UP, button().withType("submit").withText("Signup"))
)
),
footer("Test Footer").attr(Attr.CLASS, "footer").condAttr(1 == 1, Attr.ID, "id"),
script().withSrc("/testScript.js")
)
).renderFormatted();
}
@Test
public void testComplexRender() {
String expectedResult = "<!DOCTYPE html><html><head><title>Test</title></head><body><header><h1>Test Header <a href=\"http://example.com\">with link</a>.</h1></header><main><h2>Test Form</h2><div><input type=\"email\" name=\"email\" placeholder=\"Email\"><input type=\"password\" name=\"password\" placeholder=\"Password\"><button type=\"submit\">Login</button></div></main><footer class=\"footer\" id=\"id\">Test Footer</footer><script src=\"/testScript.js\"></script></body></html>";
assertThat(renderTest(), is(expectedResult));
assertThat(renderTest2(), is(expectedResult));
}
@Test
public void testComplexRender_formatted() {
assertThat(renderTest3(),
is("<!DOCTYPE html>\n"
+ "<html>\n"
+ " <head>\n"
+ " <title>\n"
+ " Test\n"
+ " </title>\n"
+ " </head>\n"
+ " <body>\n"
+ " <header>\n"
+ " <h1>\n"
+ " Test Header \n"
+ " <a href=\"http://example.com\">\n"
+ " with link\n"
+ " </a>\n"
+ " .\n"
+ " </h1>\n"
+ " </header>\n"
+ " <main>\n"
+ " <h2>\n"
+ " Test Form\n"
+ " </h2>\n"
+ " <div>\n"
+ " <input type=\"email\" name=\"email\" placeholder=\"Email\">\n"
+ " <input type=\"password\" name=\"password\" placeholder=\"Password\">\n"
+ " <button type=\"submit\">\n"
+ " Login\n"
+ " </button>\n"
+ " </div>\n"
+ " </main>\n"
+ " <footer class=\"footer\" id=\"id\">\n"
+ " Test Footer\n"
+ " </footer>\n"
+ " <script src=\"/testScript.js\">\n"
+ " </script>\n"
+ " </body>\n"
+ "</html>\n"));
}
}

View File

@@ -32,6 +32,7 @@ public class ConvenienceMethodTest {
assertThat(input().withName("test-name").render(), is("<input name=\"test-name\">"));
assertThat(input().withPlaceholder("test-placeholder").render(), is("<input placeholder=\"test-placeholder\">"));
assertThat(a().withTarget("_blank").render(), is("<a target=\"_blank\"></a>"));
assertThat(a().withTitle("Title").render(), is("<a title=\"Title\"></a>"));
assertThat(input().withType("email").render(), is("<input type=\"email\">"));
assertThat(link().withRel("stylesheet").render(), is("<link rel=\"stylesheet\">"));
assertThat(link().withRole("role").render(), is("<link role=\"role\">"));

View File

@@ -2,17 +2,11 @@ package j2html.tags;
import org.junit.Test;
import static j2html.TagCreator.body;
import static j2html.TagCreator.div;
import static j2html.TagCreator.footer;
import static j2html.TagCreator.header;
import static j2html.TagCreator.html;
import static j2html.TagCreator.iff;
import static j2html.TagCreator.main;
import static j2html.TagCreator.p;
import static j2html.TagCreator.tag;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import j2html.Config;
import static j2html.TagCreator.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
public class TagTest {
@@ -44,6 +38,19 @@ public class TagTest {
assertThat(testTag.renderCloseTag(), is("</a>"));
}
@Test
public void testSelfClosingTags() throws Exception {
Config.closeEmptyTags = true;
assertThat(img().withSrc("/test.png").render(), is("<img src=\"/test.png\"/>"));
assertThat(input().withType("text").render(), is("<input type=\"text\"/>"));
Config.closeEmptyTags = false;
}
@Test
public void testFormattedTags() throws Exception { // better test in ComplexRenderTest.java
assertThat(div(p("Hello")).renderFormatted(), is("<div>\n <p>\n Hello\n </p>\n</div>\n"));
}
@Test
public void testEquals() throws Exception {
Tag tagOne = tag("p").withClass("class").withText("Test");
@@ -51,6 +58,16 @@ public class TagTest {
assertThat(tagOne.equals(tagTwo), is(true));
}
@Test
public void testAcceptObjectValueAttribute() throws Exception {
ContainerTag complexTestTag = new ContainerTag("input")
.attr("attr1", "value1")
.attr("attr2", 2)
.attr("attr3", null);
String expectedResult = "<input attr1=\"value1\" attr2=\"2\" attr3>";
assertThat(complexTestTag.renderOpenTag(), is(expectedResult));
}
@Test
public void testWithClasses() throws Exception {
String expected = "<div class=\"c1 c2\"></div>";