├── .gitignore ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── benjiweber │ ├── html │ ├── HtmlDsl.java │ ├── StreamUtils.java │ ├── format │ │ └── Formatter.java │ ├── tags │ │ ├── A.java │ │ ├── Attr.java │ │ ├── Body.java │ │ ├── Content.java │ │ ├── Div.java │ │ ├── Head.java │ │ ├── Html.java │ │ ├── Img.java │ │ ├── Link.java │ │ ├── Literal.java │ │ ├── Meta.java │ │ ├── P.java │ │ ├── ReflectiveTag.java │ │ ├── Script.java │ │ ├── Span.java │ │ ├── Tag.java │ │ └── Title.java │ └── values │ │ ├── PixelMeasurement.java │ │ ├── Rel.java │ │ ├── ScriptType.java │ │ └── Style.java │ └── typeref │ ├── DefaultValue.java │ ├── MethodAwareConsumer.java │ ├── MethodAwareFunction.java │ ├── MethodAwarePredicate.java │ ├── MethodFinder.java │ ├── NameOf.java │ ├── NamedValue.java │ ├── Newable.java │ ├── NewableConsumer.java │ ├── Parameters.java │ └── TypeReference.java └── test └── java └── com └── benjiweber └── html └── HtmlExampleTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | *.class 3 | *.iml 4 | .idea/ 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java-html-dsl 2 | 3 | Example DSL for writing html in Java. Rework of benjiman/java-html-dsl to work with newer versions of Java 4 | 5 | This 6 | 7 | ```java 8 | String doc = 9 | html( 10 | head( 11 | title("Hello Html World"), 12 | meta($ -> $.charset = "utf-8"), 13 | link($->{ $.rel=stylesheet; $.type=css; $.href="/foo.css"; }), 14 | script($->{ $.type= javascript; $.src="/some.js"; }) 15 | ), 16 | body( 17 | div($-> $.cssClass = "article", 18 | a($-> $.href="https://benjiweber.com/", 19 | span($->$.cssClass="label", "Click Here"), 20 | img($->{$.src="/myimg.jpg"; $.width=px(25); $.height=px(25); }) 21 | ), 22 | p(span("some text"), div("block")) 23 | ) 24 | ) 25 | ).formatted() 26 | ``` 27 | Generates 28 | 29 | ```html 30 | 31 | 32 | Hello Html World 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | Click Here 41 | 42 | 43 |

44 | some text 45 |

block
46 |

47 |
48 | 49 | 50 | ``` -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.benjiweber 8 | java-html-dsl 9 | 2.0-SNAPSHOT 10 | 11 | 12 | 13 | org.apache.maven.plugins 14 | maven-compiler-plugin 15 | 3.8.1 16 | 17 | 15 18 | --enable-preview 19 | 15 20 | 15 21 | 22 | 23 | 24 | org.apache.maven.plugins 25 | maven-surefire-plugin 26 | 2.22.2 27 | 28 | --enable-preview 29 | 30 | 31 | 32 | 33 | 34 | 35 | junit 36 | junit 37 | 4.11 38 | 39 | 40 | org.mockito 41 | mockito-all 42 | 1.9.5 43 | 44 | 45 | net.sf.jtidy 46 | jtidy 47 | r938 48 | 49 | 50 | commons-lang 51 | commons-lang 52 | 2.3 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/HtmlDsl.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html; 2 | 3 | import com.benjiweber.html.tags.*; 4 | import com.benjiweber.typeref.Parameters; 5 | 6 | import java.util.List; 7 | 8 | public interface HtmlDsl extends 9 | Html.Dsl, 10 | Head.Dsl, 11 | Title.Dsl, 12 | Meta.Dsl, 13 | Link.Dsl, 14 | Script.Dsl, 15 | Body.Dsl, 16 | Div.Dsl, 17 | Span.Dsl, 18 | A.Dsl, 19 | P.Dsl, 20 | Img.Dsl { 21 | 22 | static T build(Parameters params, Tag... children) { 23 | var tag = params.get(); 24 | tag.children = List.of(children); 25 | return tag; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/StreamUtils.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html; 2 | 3 | import com.benjiweber.html.tags.Tag; 4 | 5 | import java.util.stream.Stream; 6 | 7 | import static java.util.stream.Collectors.toList; 8 | 9 | public class StreamUtils { 10 | 11 | public interface ExceptionalSupplier { 12 | R get() throws E; 13 | } 14 | public static R unchecked(ExceptionalSupplier f) { 15 | try { 16 | return f.get(); 17 | } catch (RuntimeException | Error e) { 18 | throw e; 19 | } catch ( Exception e) { 20 | throw new RuntimeException(e); 21 | } 22 | } 23 | 24 | public static Tag[] tagArrayOf(Stream children) { 25 | return children.collect(toList()).toArray(new Tag[0]); 26 | } 27 | 28 | public static T[] array(T... ts) { 29 | return ts; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/format/Formatter.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.format; 2 | 3 | import org.w3c.tidy.Tidy; 4 | 5 | import java.io.StringReader; 6 | import java.io.StringWriter; 7 | 8 | public class Formatter { 9 | 10 | public static String tidy(String html) { 11 | Tidy tidy = new Tidy(); 12 | tidy.setSmartIndent(true); 13 | tidy.setTidyMark(false); 14 | tidy.setDocType("omit"); 15 | tidy.setXmlTags(true); 16 | StringWriter writer = new StringWriter(); 17 | tidy.parse(new StringReader(html), writer); 18 | return writer.toString().replaceAll("",""); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/A.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | 3 | import com.benjiweber.typeref.Parameters; 4 | 5 | import static com.benjiweber.html.HtmlDsl.build; 6 | 7 | public class A extends ReflectiveTag implements Content { 8 | public String href; 9 | public String rel ; 10 | public interface Dsl { 11 | default A a(Parameters params, Tag... children) { 12 | return build(params, children); 13 | } 14 | default A a(Parameters params, String literal) { 15 | return build(params, new Literal(literal) ); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/Attr.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | public record Attr (String key, T value) { 3 | public boolean hasValue() { 4 | return value != null; 5 | } 6 | } -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/Body.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | 3 | import com.benjiweber.typeref.Parameters; 4 | 5 | import java.util.stream.Stream; 6 | 7 | import static com.benjiweber.html.HtmlDsl.build; 8 | import static com.benjiweber.html.StreamUtils.tagArrayOf; 9 | 10 | public class Body extends Tag { 11 | public interface Dsl { 12 | 13 | default Body body(Stream children) { 14 | return body(tagArrayOf(children)); 15 | } 16 | default Body body(Tag... children) { 17 | return build($->{},children); 18 | } 19 | default Body body(Parameters params, Tag... children) { 20 | return build(params,children); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/Content.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | 3 | public interface Content { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/Div.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | 3 | import com.benjiweber.typeref.Parameters; 4 | 5 | import static com.benjiweber.html.HtmlDsl.build; 6 | 7 | public class Div extends Tag implements Content { 8 | 9 | public interface Dsl { 10 | 11 | default Div div(String content) { 12 | return div(new Literal(content)); 13 | } 14 | default Div div(Tag... children) { 15 | return build($->{},children); 16 | } 17 | default Div div(Parameters
params, Tag... children) { 18 | return build(params,children); 19 | } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/Head.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | 3 | import com.benjiweber.typeref.Parameters; 4 | 5 | import static com.benjiweber.html.HtmlDsl.build; 6 | 7 | public class Head extends Tag { 8 | 9 | public interface Dsl { 10 | default Head head(Tag... children) { 11 | return build($->{},children); 12 | } 13 | default Head head(Parameters params, Tag... children) { 14 | return build(params,children); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/Html.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | 3 | import com.benjiweber.typeref.Parameters; 4 | 5 | import static com.benjiweber.html.HtmlDsl.build; 6 | import static com.benjiweber.html.StreamUtils.array; 7 | 8 | public class Html extends Tag { 9 | 10 | public interface Dsl { 11 | default Html html(Head head, Body body) { 12 | return build($->{}, array(head,body)); 13 | } 14 | default Html html(Parameters params, Head head, Body body) { 15 | return build(params,array(head,body)); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/Img.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | 3 | import com.benjiweber.html.values.PixelMeasurement; 4 | import com.benjiweber.typeref.Parameters; 5 | 6 | import static com.benjiweber.html.HtmlDsl.build; 7 | 8 | public class Img extends ReflectiveTag implements Content { 9 | public String src; 10 | public PixelMeasurement width; 11 | public PixelMeasurement height; 12 | 13 | public interface Dsl { 14 | default Img img(Parameters params, Tag... children) { 15 | return build(params, children); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/Link.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | 3 | import com.benjiweber.html.values.Rel; 4 | import com.benjiweber.html.values.Style; 5 | import com.benjiweber.typeref.Parameters; 6 | 7 | import static com.benjiweber.html.HtmlDsl.build; 8 | 9 | public class Link extends ReflectiveTag implements Content { 10 | public Rel rel; 11 | public Style type; 12 | public String href; 13 | 14 | public interface Dsl { 15 | default Link link(Parameters params, Tag... children) { 16 | return build(params, children); 17 | } 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/Literal.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | 3 | import org.apache.commons.lang.StringEscapeUtils; 4 | 5 | public class Literal extends Tag implements Content { 6 | public Literal(String literal) { 7 | this.innerText = literal; 8 | } 9 | 10 | @Override 11 | public String toString() { 12 | return StringEscapeUtils.escapeHtml(innerText); 13 | } 14 | } -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/Meta.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | 3 | import com.benjiweber.typeref.Parameters; 4 | 5 | import static com.benjiweber.html.HtmlDsl.build; 6 | 7 | public class Meta extends ReflectiveTag { 8 | public String charset; 9 | public interface Dsl { 10 | default Meta meta(Parameters params, Tag... children) { 11 | return build(params, children); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/P.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | 3 | import com.benjiweber.typeref.Parameters; 4 | 5 | import static com.benjiweber.html.HtmlDsl.build; 6 | 7 | public class P extends Tag implements Content { 8 | public interface Dsl { 9 | 10 | default P p(String literal) { 11 | return p(new Literal(literal)); 12 | } 13 | default P p(T... children) { 14 | return build($->{}, children); 15 | } 16 | default P p(Parameters

params, T... children) { 17 | return build(params, children); 18 | } 19 | default P p(Parameters

params, String literal) { 20 | return build(params, new Literal(literal) ); 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/ReflectiveTag.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | 3 | import java.util.List; 4 | import java.util.stream.Stream; 5 | 6 | import static com.benjiweber.html.StreamUtils.unchecked; 7 | import static java.util.stream.Collectors.toList; 8 | 9 | public class ReflectiveTag extends Tag { 10 | public List attrs() { 11 | return Stream.concat( 12 | super.attrs().stream(), 13 | Stream.of(getClass().getDeclaredFields()) 14 | .filter(field -> !field.getType().isAssignableFrom(List.class)) 15 | .peek(field -> field.setAccessible(true)) 16 | .map(field -> unchecked(() -> new Attr(field.getName(), field.get(this)))) 17 | .filter(Attr::hasValue) 18 | ).collect(toList()); 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/com/benjiweber/html/tags/Script.java: -------------------------------------------------------------------------------- 1 | package com.benjiweber.html.tags; 2 | 3 | import com.benjiweber.html.values.Rel; 4 | import com.benjiweber.html.values.ScriptType; 5 | import com.benjiweber.html.values.Style; 6 | import com.benjiweber.typeref.Parameters; 7 | 8 | import static com.benjiweber.html.HtmlDsl.build; 9 | 10 | public class Script extends ReflectiveTag { 11 | public ScriptType type; 12 | public String src; 13 | 14 | public interface Dsl { 15 | default Script script(Parameters 24 | 25 | 26 |

27 | 28 | Click Here 29 | 30 | 31 |

32 | some text 33 |

block
34 |

35 |
36 | 37 | 38 | """.trim(), 39 | html( 40 | head( 41 | title("Hello Html World"), 42 | meta($ -> $.charset = "utf-8"), 43 | link($->{ $.rel=stylesheet; $.type=css; $.href="/foo.css"; }), 44 | script($->{ $.type= javascript; $.src="/some.js"; }) 45 | ), 46 | body( 47 | div($-> $.cssClass = "article", 48 | a($-> $.href="https://benjiweber.com/", 49 | span($->$.cssClass="label", "Click Here"), 50 | img($->{$.src="/myimg.jpg"; $.width=px(25); $.height=px(25); }) 51 | ), 52 | p(span("some text"), div("block")) 53 | ) 54 | ) 55 | ).formatted() 56 | ); 57 | } 58 | 59 | @Test 60 | public void example_generated_doc() { 61 | assertEquals( 62 | """ 63 | 64 | 65 | 66 | 67 | 68 |

Paragraph one

69 |

Paragraph two

70 |

Paragraph three

71 | 72 | 73 | """.trim(), 74 | html( 75 | head( 76 | meta($ -> $.charset = "utf-8") 77 | ), 78 | body( 79 | Stream.of("one","two","three") 80 | .map(number -> "Paragraph " + number) 81 | .map(content -> p(content)) 82 | ) 83 | ).formatted() 84 | ); 85 | } 86 | 87 | @Test 88 | public void example_escaped_user_content() { 89 | assertEquals( 90 | """ 91 | 92 | 93 | 94 | 95 | 96 |

<script src="attack.js"></script>

97 | 98 | 99 | """.trim(), 100 | html( 101 | head( 102 | meta($-> $.charset = "utf-8") 103 | ), 104 | body( 105 | p("") 106 | ) 107 | ).formatted() 108 | ); 109 | } 110 | 111 | 112 | } 113 | --------------------------------------------------------------------------------