Web component support (#211)

* Foundational components for reading/processing Wattsi files into classes.

* Introducing tests for compliance with wattsi definitions.

- Several elements have been found to be missing from the code generator.
- A long-term strategy for dealing with obsolete elements will be needed.  Should we remove and change the API?  Or should we deprecate and leave in-place?
- Found one element that was accidentally added to the code generator but never used.  Hopefully.
- Fixes will be applied in following commits.

* Added newer elements/tags and cleaned up invalid Tag class.

- Added DataTag.
- Added SlotTag and TemplateTag to support web components.
- Added global attributes to support web components.
- Removed GenerateTag.  Accidentally introduced by string replacement in code generator.

* Upgrading revapi and maven plugin. Configured revapi to ignore removal of GenerateTag.

- The GenerateTag class was created accidentally.  It does not have any representation in the HTML standard is should not be provided in this library.
This commit is contained in:
Scott Embler
2022-06-29 20:13:46 -04:00
committed by GitHub
parent c2177d0584
commit d1c404d5db
19 changed files with 129126 additions and 82 deletions

View File

@@ -1,74 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.j2html</groupId>
<artifactId>j2htmlcodegen</artifactId>
<version>1.0-SNAPSHOT</version>
<groupId>com.j2html</groupId>
<artifactId>j2htmlcodegen</artifactId>
<version>1.0-SNAPSHOT</version>
<name>j2htmlcodegen</name>
<url>https://j2html.com/</url>
<name>j2htmlcodegen</name>
<url>https://j2html.com/</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@@ -63,7 +63,7 @@ public final class TagCreatorCodeGenerator {
}
// This is a method that contains all ContainerTags, there is nothing below it
static List<String> emptyTags() {
public static List<String> emptyTags() {
return Arrays.asList(
"area",
"base",
@@ -84,7 +84,7 @@ public final class TagCreatorCodeGenerator {
);
}
static List<String> containerTags() {
public static List<String> containerTags() {
return Arrays.asList(
"a",
"abbr",
@@ -103,6 +103,7 @@ public final class TagCreatorCodeGenerator {
"cite",
"code",
"colgroup",
"data",
"datalist",
"dd",
"del",
@@ -134,7 +135,6 @@ public final class TagCreatorCodeGenerator {
"label",
"legend",
"li",
"generate",
"main",
"map",
"mark",
@@ -161,6 +161,7 @@ public final class TagCreatorCodeGenerator {
"script",
"section",
"select",
"slot",
"small",
"span",
"strong",
@@ -171,6 +172,7 @@ public final class TagCreatorCodeGenerator {
"table",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",

View File

@@ -0,0 +1,94 @@
package j2html_codegen.generators;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import j2html_codegen.wattsi.AttributeDefinition;
import j2html_codegen.wattsi.ElementDefinition;
import j2html_codegen.wattsi.WattsiSource;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import javax.lang.model.element.Modifier;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class WattsiGenerator {
public static void main(String... args) throws IOException {
Path source = Paths.get(args[0]);
Document doc = Jsoup.parse(source.toFile(), "UTF-8", "https://html.spec.whatwg.org/");
WattsiSource wattsi = new WattsiSource(doc);
List<ElementDefinition> elements = wattsi.elementDefinitions();
List<AttributeDefinition> attributes = wattsi.attributeDefinitions();
// for (ElementDefinition element : elements) {
// System.out.println((element.isObsolete() ? "!" : "") + element.name());
// for (AttributeDefinition attribute : attributes) {
// if (attribute.appliesTo(element)) {
// System.out.println(" " + (attribute.isObsolete() ? "!" : "") + attribute.name());
// }
// }
// System.out.println();
// }
for (ElementDefinition element : elements) {
ClassName className = ClassName.get(
"com.j2html",
capitalize(element.name()) + "Tag"
);
TypeSpec.Builder type = TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC);
if (element.isObsolete()) {
type.addAnnotation(Deprecated.class);
}
for (AttributeDefinition attribute : attributes) {
if (attribute.appliesTo(element)) {
String name = methodName("with", attribute.name().split("-"));
MethodSpec.Builder setter = MethodSpec.methodBuilder(name)
.addModifiers(Modifier.PUBLIC)
.returns(className)
.addStatement("return this");
if(attribute.isObsolete()){
setter.addAnnotation(Deprecated.class);
}
type.addMethod(setter.build());
}
}
// System.out.println(type.build().toString());
}
System.out.println(doc.select("dfn"));
}
private static String methodName(String prefix, String... words){
String[] tmp = new String[words.length + 1];
tmp[0] = prefix;
for(int i = 0; i < words.length; i++){
tmp[i+1] = words[i];
}
return methodName(tmp);
}
private static String methodName(String... words){
String[] camelCase = new String[words.length];
camelCase[0] = words[0];
for(int i = 1; i < words.length; i++){
camelCase[i] = capitalize(words[i]);
}
return String.join("", camelCase);
}
private static String capitalize(String word){
return word.substring(0,1).toUpperCase() + word.substring(1).toLowerCase();
}
}

View File

@@ -80,7 +80,7 @@ public final class AttributesList {
new AttrD("min", true, "input","meter"),
new AttrD("multiple", false, "input","select"),
new AttrD("muted", false, "video","audio"),
new AttrD("name", true, "button","fieldset","form","iframe","input","map","meta","object","output","param","select","textarea"),
new AttrD("name", true, "button","fieldset","form","iframe","input","map","meta","object","output","param","select","slot","textarea"),
new AttrD("novalidate", false, "form"),
new AttrD("onabort", true, "audio","embed","img","object","video"),
new AttrD("onafterprint", true, "body"),
@@ -174,7 +174,7 @@ public final class AttributesList {
//new AttrD("translate"),// global attribute
new AttrD("type", true, "a","button","embed","input","link","menu","object","script","source","style"),
new AttrD("usemap", true, "img","object"),
new AttrD("value", true, "button","input","li","option","meter","progress","param"),
new AttrD("value", true, "button","data","input","li","option","meter","progress","param"),
new AttrD("width", true, "canvas","embed","iframe","img","input","object","video"),
new AttrD("wrap", true, "textarea")
);

View File

@@ -0,0 +1,10 @@
package j2html_codegen.wattsi;
public interface AttributeDefinition {
String name();
boolean appliesTo(ElementDefinition element);
boolean isObsolete();
}

View File

@@ -0,0 +1,9 @@
package j2html_codegen.wattsi;
public interface ElementDefinition {
String name();
boolean isObsolete();
}

View File

@@ -0,0 +1,204 @@
package j2html_codegen.wattsi;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
public class WattsiSource {
private final Document doc;
private final Set<Reference> obsolete = new HashSet<>();
public WattsiSource(Document doc) {
this.doc = doc;
// Find where obsolete elements are defined or referenced.
Elements obsoleteElements = doc.select("p:contains(Elements in the following list are entirely obsolete) + dl");
// Convert definitions into references to record obsolete elements.
obsoleteElements.select("dt > dfn[element]")
.stream()
.map(WattsiElement::new)
.map(WattsiElement::reference)
.forEach(obsolete::add);
// Extract references to record obsolete elements.
obsoleteElements.select("dt > code")
.stream()
.map(Element::childNodes)
.map(Reference::from)
.forEach(obsolete::add);
// Find where obsolete attributes are defined or referenced.
Elements obsoleteAttributes = doc.select("p:contains(The following attributes are obsolete) + dl");
// Convert definitions into references to record obsolete attributes.
obsoleteAttributes.select("dt > dfn[element-attr]").stream()
.map(WattsiAttribute::new)
.map(WattsiAttribute::reference)
.forEach(obsolete::add);
// System.out.println(obsoleteAttributes.select("dt"));
// obsoleteAttributes.select("dt > code").stream()
// .map(Element::childNodes)
// .map(Reference::from)
// .forEach(System.err::println);
// System.out.println(
// doc.select("dfn[obsolete]")
// );
}
public List<ElementDefinition> elementDefinitions() {
return doc.select("dfn[element]").stream()
.map(WattsiElement::new)
.collect(toList());
}
public List<AttributeDefinition> attributeDefinitions() {
return doc.select("dfn[element-attr]").stream()
.map(WattsiAttribute::new)
.collect(toList());
}
public class WattsiElement implements ElementDefinition {
private final Element dfn;
WattsiElement(Element dfn) {
if (!"dfn".equals(dfn.tagName())) {
throw new IllegalArgumentException("Element cannot be defined from: " + dfn);
}
if (!dfn.hasAttr("element")) {
throw new IllegalArgumentException("Does not define an element: " + dfn);
}
if (dfn.childrenSize() != 1) {
throw new IllegalArgumentException("Element cannot have multiple definitions: " + dfn);
}
this.dfn = dfn;
}
private Reference reference() {
return Reference.from(dfn.childNodes());
}
@Override
public String name() {
if (dfn.hasAttr("data-x")) {
return dfn.attr("data-x");
}
return Reference.from(dfn.childNodes()).key;
}
@Override
public boolean isObsolete() {
return obsolete.contains(reference());
}
}
public class WattsiAttribute implements AttributeDefinition {
private final Element dfn;
WattsiAttribute(Element dfn) {
if (!"dfn".equals(dfn.tagName())) {
throw new IllegalArgumentException("Attribute cannot be defined from: " + dfn);
}
if (!dfn.hasAttr("element-attr")) {
throw new IllegalArgumentException("Does not define an attribute: " + dfn);
}
if (dfn.childrenSize() != 1) {
throw new IllegalArgumentException("Attribute cannot have multiple definitions: " + dfn);
}
this.dfn = dfn;
}
private Reference reference() {
return Reference.from(dfn.childNodes());
}
@Override
public String name() {
return reference().text;
}
private List<String> targets() {
if (dfn.hasAttr("for")) {
return Arrays.asList(dfn.attr("for").trim().split(","));
}
return new ArrayList<>();
}
@Override
public boolean appliesTo(ElementDefinition element) {
return targets().contains(element.name());
}
@Override
public boolean isObsolete() {
return obsolete.contains(reference());
}
}
private static class Reference {
private final String key;
private final String text;
Reference(String key, String text) {
this.key = key;
this.text = text;
}
@Override
public String toString() {
return key + "[" + text + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Reference reference = (Reference) o;
return key.equals(reference.key);
}
@Override
public int hashCode() {
return Objects.hash(key);
}
public static Reference from(List<Node> nodes) {
if (nodes.stream().allMatch(n -> n instanceof TextNode)) {
String txt = nodes.stream()
.map(n -> (TextNode) n)
.map(TextNode::text)
.collect(Collectors.joining(" "));
return new Reference(txt, txt);
}
for (Node node : nodes) {
if (node instanceof Element) {
Element element = (Element) node;
if (element.is("code") || element.is("span")) {
if (element.hasAttr("data-x")) {
return new Reference(element.attr("data-x").toLowerCase(), element.text());
} else {
return new Reference(element.text().toLowerCase(), element.text());
}
}
}
}
return null;
}
}
}

View File

@@ -0,0 +1,77 @@
package j2html_codegen;
import j2html_codegen.generators.TagCreatorCodeGenerator;
import j2html_codegen.wattsi.ElementDefinition;
import j2html_codegen.wattsi.WattsiSource;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertEquals;
public class CodeGeneratorComplianceTests {
private WattsiSource specification;
@Before
public void setUp() throws IOException {
Path source = Paths.get("src","test","resources","2022-01.wattsi");
Document doc = Jsoup.parse(source.toFile(), "UTF-8", "https://html.spec.whatwg.org/");
specification = new WattsiSource(doc);
}
private Set<String> generatedElements(){
Set<String> elements = new HashSet<>();
elements.addAll(TagCreatorCodeGenerator.emptyTags());
elements.addAll(TagCreatorCodeGenerator.containerTags());
return elements;
}
private Set<String> specifiedElements(WattsiSource source){
Set<String> elements = new HashSet<>();
for(ElementDefinition element : source.elementDefinitions()){
elements.add(element.name());
}
return elements;
}
@Test
@Ignore
// TODO restore this test once a policy has been determined for obsolete elements.
public void all_wattsi_elements_are_defined_in_the_code_generator() {
Set<String> generated = generatedElements();
List<String> undefined = specification.elementDefinitions().stream()
.filter(element -> !element.isObsolete())
.filter(element -> !generated.contains(element.name()))
.map(ElementDefinition::name)
.collect(toList());
assertEquals("HTML elements are missing", emptyList(), undefined);
// Currently missing (and mostly deprecated):
// hgroup
}
@Test
public void only_wattsi_elements_are_defined_in_the_code_generator(){
Set<String> specified = specifiedElements(specification);
List<String> invalid = generatedElements().stream()
.filter(element -> !specified.contains(element))
.collect(toList());
assertEquals("HTML elements are invalid", emptyList(), invalid);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@
<groupId>com.j2html</groupId>
<artifactId>j2html</artifactId>
<version>1.5.1-SNAPSHOT</version>
<version>1.6.0-SNAPSHOT</version>
<name>j2html</name>
<description>Java to HTML builder with a fluent API</description>
@@ -168,18 +168,33 @@
<plugin>
<groupId>org.revapi</groupId>
<artifactId>revapi-maven-plugin</artifactId>
<version>0.14.3</version>
<version>0.14.6</version>
<dependencies>
<dependency>
<groupId>org.revapi</groupId>
<artifactId>revapi-java</artifactId>
<version>0.24.2</version>
<version>0.26.1</version>
</dependency>
</dependencies>
<configuration>
<oldArtifacts>
<artifact>com.j2html:j2html:1.5.0</artifact>
</oldArtifacts>
<analysisConfiguration>
<revapi.differences>
<differences>
<item>
<ignore>true</ignore>
<code>java.class.removed</code>
<old>class j2html.tags.specialized.GenerateTag</old>
<justification>
This class should never have been used. It was introduced
accidentally by find+replace in the code generator class.
</justification>
</item>
</differences>
</revapi.differences>
</analysisConfiguration>
</configuration>
<executions>
<execution>

View File

@@ -461,6 +461,13 @@ public class TagCreator {
public static ColgroupTag colgroup (Attr.ShortForm shortAttr, String text) { return Attr.addTo( new ColgroupTag().withText(text), shortAttr); }
public static ColgroupTag colgroup (Attr.ShortForm shortAttr, DomContent... dc) { return Attr.addTo( new ColgroupTag().with(dc), shortAttr); }
public static DataTag data () { return new DataTag(); }
public static DataTag data (String text) { return new DataTag().withText(text); }
public static DataTag data (DomContent... dc) { return new DataTag().with(dc); }
public static DataTag data (Attr.ShortForm shortAttr) { return Attr.addTo( new DataTag(), shortAttr); }
public static DataTag data (Attr.ShortForm shortAttr, String text) { return Attr.addTo( new DataTag().withText(text), shortAttr); }
public static DataTag data (Attr.ShortForm shortAttr, DomContent... dc) { return Attr.addTo( new DataTag().with(dc), shortAttr); }
public static DatalistTag datalist () { return new DatalistTag(); }
public static DatalistTag datalist (String text) { return new DatalistTag().withText(text); }
public static DatalistTag datalist (DomContent... dc) { return new DatalistTag().with(dc); }
@@ -846,6 +853,13 @@ public class TagCreator {
public static SelectTag select (Attr.ShortForm shortAttr, String text) { return Attr.addTo( new SelectTag().withText(text), shortAttr); }
public static SelectTag select (Attr.ShortForm shortAttr, DomContent... dc) { return Attr.addTo( new SelectTag().with(dc), shortAttr); }
public static SlotTag slot () { return new SlotTag(); }
public static SlotTag slot (String text) { return new SlotTag().withText(text); }
public static SlotTag slot (DomContent... dc) { return new SlotTag().with(dc); }
public static SlotTag slot (Attr.ShortForm shortAttr) { return Attr.addTo( new SlotTag(), shortAttr); }
public static SlotTag slot (Attr.ShortForm shortAttr, String text) { return Attr.addTo( new SlotTag().withText(text), shortAttr); }
public static SlotTag slot (Attr.ShortForm shortAttr, DomContent... dc) { return Attr.addTo( new SlotTag().with(dc), shortAttr); }
public static SmallTag small () { return new SmallTag(); }
public static SmallTag small (String text) { return new SmallTag().withText(text); }
public static SmallTag small (DomContent... dc) { return new SmallTag().with(dc); }
@@ -916,6 +930,13 @@ public class TagCreator {
public static TdTag td (Attr.ShortForm shortAttr, String text) { return Attr.addTo( new TdTag().withText(text), shortAttr); }
public static TdTag td (Attr.ShortForm shortAttr, DomContent... dc) { return Attr.addTo( new TdTag().with(dc), shortAttr); }
public static TemplateTag template () { return new TemplateTag(); }
public static TemplateTag template (String text) { return new TemplateTag().withText(text); }
public static TemplateTag template (DomContent... dc) { return new TemplateTag().with(dc); }
public static TemplateTag template (Attr.ShortForm shortAttr) { return Attr.addTo( new TemplateTag(), shortAttr); }
public static TemplateTag template (Attr.ShortForm shortAttr, String text) { return Attr.addTo( new TemplateTag().withText(text), shortAttr); }
public static TemplateTag template (Attr.ShortForm shortAttr, DomContent... dc) { return Attr.addTo( new TemplateTag().with(dc), shortAttr); }
public static TextareaTag textarea () { return new TextareaTag(); }
public static TextareaTag textarea (String text) { return new TextareaTag().withText(text); }
public static TextareaTag textarea (DomContent... dc) { return new TextareaTag().with(dc); }

View File

@@ -53,6 +53,7 @@ public abstract class Attr {
public static final String HTTP_EQUIV = "http-equiv";
public static final String ICON = "icon";
public static final String ID = "id";
public static final String IS = "is";
public static final String ISMAP = "ismap";
public static final String ITEMPROP = "itemprop";
public static final String KEYTYPE = "keytype";
@@ -96,6 +97,7 @@ public abstract class Attr {
public static final String SHAPE = "shape";
public static final String SIZE = "size";
public static final String SIZES = "sizes";
public static final String SLOT = "slot";
public static final String SPAN = "span";
public static final String SPELLCHECK = "spellcheck";
public static final String SRC = "src";

View File

@@ -162,8 +162,12 @@ public abstract class Tag<T extends Tag<T>> extends DomContent implements IInsta
public T withId(String id) { return attr(Attr.ID, id); }
public T withIs(String element){ return attr(Attr.IS, element); }
public T withLang(String lang) { return attr(Attr.LANG, lang); }
public T withSlot(String name){ return attr(Attr.SLOT, name); }
public T isSpellcheck(){ return attr(Attr.SPELLCHECK, "true"); }
public T withStyle(String style) { return attr(Attr.STYLE, style); }
@@ -191,8 +195,12 @@ public abstract class Tag<T extends Tag<T>> extends DomContent implements IInsta
public T withCondId(boolean condition, String id) { return condAttr(condition, Attr.ID, id); }
public T withCondIs(boolean condition, String element){ return condAttr(condition, Attr.IS, element); }
public T withCondLang(boolean condition, String lang) { return condAttr(condition, Attr.LANG, lang); }
public T withCondSlot(boolean condition, String name){ return condAttr(condition, Attr.SLOT, name); }
public T withCondSpellcheck(boolean condition){ return attr(Attr.SPELLCHECK, (condition)?"true":"false"); }
public T withCondStyle(boolean condition, String style) { return condAttr(condition, Attr.STYLE, style); }

View File

@@ -0,0 +1,11 @@
package j2html.tags.specialized;
import j2html.tags.ContainerTag;
import j2html.tags.attributes.IValue;
public final class DataTag extends ContainerTag<DataTag>
implements IValue<DataTag> {
public DataTag() {
super("data");
}
}

View File

@@ -1,9 +0,0 @@
package j2html.tags.specialized;
import j2html.tags.ContainerTag;
public final class GenerateTag extends ContainerTag<GenerateTag> {
public GenerateTag() {
super("generate");
}
}

View File

@@ -0,0 +1,11 @@
package j2html.tags.specialized;
import j2html.tags.ContainerTag;
import j2html.tags.attributes.IName;
public final class SlotTag extends ContainerTag<SlotTag>
implements IName<SlotTag> {
public SlotTag() {
super("slot");
}
}

View File

@@ -0,0 +1,9 @@
package j2html.tags.specialized;
import j2html.tags.ContainerTag;
public final class TemplateTag extends ContainerTag<TemplateTag> {
public TemplateTag() {
super("template");
}
}

View File

@@ -222,6 +222,7 @@ public class TagCreatorTest {
assertThat(cite("Text").render(), is("<cite>Text</cite>"));
assertThat(code().render(), is("<code></code>"));
assertThat(colgroup().render(), is("<colgroup></colgroup>"));
assertThat(data().render(), is("<data></data>"));
assertThat(datalist().render(), is("<datalist></datalist>"));
assertThat(dd().render(), is("<dd></dd>"));
assertThat(dd("Text").render(), is("<dd>Text</dd>"));
@@ -287,6 +288,7 @@ public class TagCreatorTest {
assertThat(output().render(), is("<output></output>"));
assertThat(p().render(), is("<p></p>"));
assertThat(p("Text").render(), is("<p>Text</p>"));
assertThat(picture().render(), is("<picture></picture>"));
assertThat(pre().render(), is("<pre></pre>"));
assertThat(progress().render(), is("<progress></progress>"));
assertThat(q().render(), is("<q></q>"));
@@ -300,6 +302,8 @@ public class TagCreatorTest {
assertThat(script().render(), is("<script></script>"));
assertThat(section().render(), is("<section></section>"));
assertThat(select().render(), is("<select></select>"));
assertThat(slot().render(), is("<slot></slot>"));
assertThat(slot("Text").render(), is("<slot>Text</slot>"));
assertThat(small().render(), is("<small></small>"));
assertThat(small("Text").render(), is("<small>Text</small>"));
assertThat(span().render(), is("<span></span>"));
@@ -317,6 +321,7 @@ public class TagCreatorTest {
assertThat(tbody().render(), is("<tbody></tbody>"));
assertThat(td().render(), is("<td></td>"));
assertThat(td("Text").render(), is("<td>Text</td>"));
assertThat(template("Text").render(), is("<template>Text</template>"));
assertThat(textarea().render(), is("<textarea></textarea>"));
assertThat(tfoot().render(), is("<tfoot></tfoot>"));
assertThat(th().render(), is("<th></th>"));

View File

@@ -40,6 +40,17 @@ public class NewsView {
"News and releases",
section(attrs("#news"),
newsPost(
"j2html 1.6.0 adds support for web components and newer HTML elements. (Jun 2022)",
"1.6.0",
false,
join("Added", code("TagCreator::data"), "to provide machine-readable translations."),
join("Added", code("TagCreator::picture"), "to provide alternative versions of an image for different display/device scenarios."),
join("Added", code("TagCreator::slot"), "to provide placeholders for web components."),
join("Added", code("TagCreator::template"), "to provide templating of content fragments for web components."),
join("Added global attributes used for web components.")
),
newsPost(
"j2html 1.5.0 enhances factory methods and class types, overhauls HTML rendering, introduces support for Java modules and fixes several bugs (Jun 2021)",
"1.5.0",