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:
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package j2html_codegen.wattsi;
|
||||
|
||||
public interface AttributeDefinition {
|
||||
|
||||
String name();
|
||||
|
||||
boolean appliesTo(ElementDefinition element);
|
||||
|
||||
boolean isObsolete();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package j2html_codegen.wattsi;
|
||||
|
||||
public interface ElementDefinition {
|
||||
|
||||
String name();
|
||||
|
||||
boolean isObsolete();
|
||||
|
||||
}
|
||||
204
code_gen/src/main/java/j2html_codegen/wattsi/WattsiSource.java
Normal file
204
code_gen/src/main/java/j2html_codegen/wattsi/WattsiSource.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user