Maven multi module support (#222)
* Restructuring to a Maven multi-module project. - Renamed directories to match artifact ids. - Added parent POM. - Added developers to parent POM. - Added dependency management and plugin management. No expectation that the build or release process is fully functional. - Extracted versions into properties like other projects. * Fixed file paths for workflow and documentation. - Added junit to j2html-codegen module. * Temporarily setting packaging for j2html-codegen to pom, to allow workflow to complete. * Removed copied configuration of maven-jar-plugin from parent POM. * Integrating code generation into the main build process. - j2html-codegen is now supplying a Maven plugin that can read a model file and generate corresponding attribute and tag classes as part of the project build. - j2html classes that would conflict with the generated classes have been removed. * Targeting LTS versions only. - JDK 9 & 10 support ended in 2018
This commit is contained in:
101
j2html-codegen/.gitignore
vendored
Normal file
101
j2html-codegen/.gitignore
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
# Created by https://www.gitignore.io
|
||||
|
||||
### Intellij ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
|
||||
|
||||
*.iml
|
||||
|
||||
## Directory-based project format:
|
||||
.idea/
|
||||
# if you remove the above rule, at least ignore the following:
|
||||
|
||||
# User-specific stuff:
|
||||
# .idea/workspace.xml
|
||||
# .idea/tasks.xml
|
||||
# .idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
# .idea/dataSources.ids
|
||||
# .idea/dataSources.xml
|
||||
# .idea/sqlDataSources.xml
|
||||
# .idea/dynamic.xml
|
||||
# .idea/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
# .idea/gradle.xml
|
||||
# .idea/libraries
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
# .idea/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
|
||||
|
||||
### Windows ###
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
|
||||
### Java ###
|
||||
*.class
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
|
||||
### Maven ###
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
|
||||
### Eclipse ###
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
buildNumber.properties
|
||||
86
j2html-codegen/pom.xml
Normal file
86
j2html-codegen/pom.xml
Normal file
@@ -0,0 +1,86 @@
|
||||
<?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>
|
||||
|
||||
<parent>
|
||||
<groupId>com.j2html</groupId>
|
||||
<artifactId>j2html-parent</artifactId>
|
||||
<version>1.6.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>j2html-codegen</name>
|
||||
<artifactId>j2html-codegen-maven-plugin</artifactId>
|
||||
<packaging>maven-plugin</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-plugin-api</artifactId>
|
||||
<version>3.8.4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugin-tools</groupId>
|
||||
<artifactId>maven-plugin-annotations</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-project</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
Note: The use of version properties below is to avoid
|
||||
redefining these versions when using this maven plugin
|
||||
in other projects. Using the more concise dependency-
|
||||
management strategy for versioning doesn't work when
|
||||
another project tries to detect this plugin's dependency
|
||||
versions.
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>${jsoup.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup</groupId>
|
||||
<artifactId>javapoet</artifactId>
|
||||
<version>${javapoet.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-plugin-plugin</artifactId>
|
||||
<version>3.6.4</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
33
j2html-codegen/src/main/java/com/j2html/codegen/App.java
Normal file
33
j2html-codegen/src/main/java/com/j2html/codegen/App.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package com.j2html.codegen;
|
||||
|
||||
import com.j2html.codegen.generators.SpecializedTagClassCodeGenerator;
|
||||
import com.j2html.codegen.generators.AttributeInterfaceCodeGenerator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public final class App
|
||||
{
|
||||
public static void main( String[] args )
|
||||
{
|
||||
final Path relPath = Paths.get("../library/src/main/java/j2html/");
|
||||
final Path absPath = relPath.toAbsolutePath();
|
||||
|
||||
System.out.println("writing in "+absPath);
|
||||
|
||||
//decide if the files should be
|
||||
//deleted or generated
|
||||
final boolean delete = false;
|
||||
|
||||
try {
|
||||
AttributeInterfaceCodeGenerator.generate(absPath, delete);
|
||||
SpecializedTagClassCodeGenerator.generate(absPath, delete);
|
||||
//TagCreatorCodeGenerator.print();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
//don't forget to auto-reformat the generated code.
|
||||
}
|
||||
}
|
||||
50
j2html-codegen/src/main/java/com/j2html/codegen/Export.java
Normal file
50
j2html-codegen/src/main/java/com/j2html/codegen/Export.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package com.j2html.codegen;
|
||||
|
||||
import com.j2html.codegen.model.AttrD;
|
||||
|
||||
import static com.j2html.codegen.generators.TagCreatorCodeGenerator.containerTags;
|
||||
import static com.j2html.codegen.generators.TagCreatorCodeGenerator.emptyTags;
|
||||
import static com.j2html.codegen.model.AttributesList.attributesDescriptive;
|
||||
import static com.j2html.codegen.model.AttributesList.getCustomAttributesForHtmlTag;
|
||||
import static java.lang.System.*;
|
||||
|
||||
public class Export {
|
||||
|
||||
public static void main(String[] args){
|
||||
for (final String tag : emptyTags()) {
|
||||
out.print("EMPTY-ELEMENT[");
|
||||
out.print(tag);
|
||||
out.print("]");
|
||||
out.println();
|
||||
}
|
||||
for (final String tag : containerTags()) {
|
||||
out.print("ELEMENT[");
|
||||
out.print(tag);
|
||||
out.print("]");
|
||||
out.println();
|
||||
}
|
||||
|
||||
out.println();
|
||||
|
||||
for(AttrD attr : attributesDescriptive()){
|
||||
if(attr.hasArgument){
|
||||
out.print("STRING");
|
||||
}else{
|
||||
out.print("BOOLEAN");
|
||||
}
|
||||
out.print("[");
|
||||
out.print(attr.attr);
|
||||
out.print("]");
|
||||
out.println();
|
||||
for(String tag : attr.tags){
|
||||
out.print("ATTRIBUTE[");
|
||||
out.print(tag);
|
||||
out.print(":");
|
||||
out.print(attr.attr);
|
||||
out.print("]");
|
||||
out.println();
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
199
j2html-codegen/src/main/java/com/j2html/codegen/Generator.java
Normal file
199
j2html-codegen/src/main/java/com/j2html/codegen/Generator.java
Normal file
@@ -0,0 +1,199 @@
|
||||
package com.j2html.codegen;
|
||||
|
||||
import com.j2html.codegen.Model.Node;
|
||||
import com.squareup.javapoet.*;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.j2html.codegen.Model.Metadata.ON_OFF;
|
||||
import static com.j2html.codegen.Model.Metadata.SELF_CLOSING;
|
||||
|
||||
public class Generator {
|
||||
|
||||
public static final ClassName INSTANCE = ClassName.get("j2html.tags", "IInstance");
|
||||
public static final ClassName TAG = ClassName.get("j2html.tags", "Tag");
|
||||
public static final ClassName EMPTY_TAG = ClassName.get("j2html.tags", "EmptyTag");
|
||||
public static final ClassName CONTAINER_TAG = ClassName.get("j2html.tags", "ContainerTag");
|
||||
|
||||
public static void main(String... args) throws IOException {
|
||||
Path path = Paths.get("j2html-codegen", "src", "test", "resources", "html.model");
|
||||
String definitions = new String(Files.readAllBytes(path));
|
||||
Model model = new Model();
|
||||
Parser.parse(definitions, model);
|
||||
|
||||
Path dir = Paths.get("/j2html/generated-source");
|
||||
Files.createDirectories(dir);
|
||||
generate(dir, "j2html.tags.attributes", "j2html.tags.specialized", model);
|
||||
}
|
||||
|
||||
public static void generate(Path root, String attributePkg, String elementPkg, Model model) throws IOException {
|
||||
Map<String, JavaFile> attributes = generateAttributePackage(attributePkg, model);
|
||||
for (JavaFile file : attributes.values()) {
|
||||
file.writeTo(root);
|
||||
}
|
||||
|
||||
Map<String, JavaFile> elements = generateElementPackage(elementPkg, model, attributes);
|
||||
for (JavaFile file : elements.values()) {
|
||||
file.writeTo(root);
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, JavaFile> generateElementPackage(String pkg, Model model, Map<String, JavaFile> attributes) {
|
||||
Map<String, JavaFile> files = new HashMap<>();
|
||||
|
||||
// Convert all elements into classes.
|
||||
for (Node element : model.elements()) {
|
||||
ClassName className = ClassName.get(pkg, capitalize(element.name) + "Tag");
|
||||
|
||||
TypeSpec.Builder type = defineElementClass(element, className);
|
||||
|
||||
// Assign attributes to this element.
|
||||
for (Node attribute : element.children) {
|
||||
JavaFile file = attributes.get(attribute.name);
|
||||
type.addSuperinterface(
|
||||
ParameterizedTypeName.get(
|
||||
ClassName.get(file.packageName, file.typeSpec.name),
|
||||
className
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
files.put(
|
||||
element.name,
|
||||
JavaFile.builder(pkg, type.build())
|
||||
.skipJavaLangImports(true)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private static Map<String, JavaFile> generateAttributePackage(String pkg, Model model) {
|
||||
Map<String, JavaFile> files = new HashMap<>();
|
||||
|
||||
// Convert all attributes into classes.
|
||||
for (Node attribute : model.attributes()) {
|
||||
TypeSpec.Builder type = defineAttributeClass(pkg, attribute);
|
||||
|
||||
if (attribute.type.equals(Node.Type.STRING)) {
|
||||
defineStringAttributeMethods(attribute, type);
|
||||
} else if (attribute.type.equals(Node.Type.BOOLEAN) && !attribute.is(ON_OFF)) {
|
||||
defineBooleanAttributeMethods(attribute, type);
|
||||
} else if (attribute.type.equals(Node.Type.BOOLEAN) && attribute.is(ON_OFF)) {
|
||||
defineOnOffAttributeMethods(attribute, type);
|
||||
}
|
||||
|
||||
files.put(
|
||||
attribute.name,
|
||||
JavaFile.builder(pkg, type.build())
|
||||
.skipJavaLangImports(true)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private static TypeSpec.Builder defineElementClass(Node element, ClassName className) {
|
||||
MethodSpec constructor = MethodSpec.constructorBuilder()
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addStatement("super(\"" + element.name + "\")")
|
||||
.build();
|
||||
|
||||
TypeSpec.Builder type = TypeSpec.classBuilder(className)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.superclass(
|
||||
ParameterizedTypeName.get(element.is(SELF_CLOSING) ? EMPTY_TAG : CONTAINER_TAG, className)
|
||||
)
|
||||
.addMethod(constructor);
|
||||
return type;
|
||||
}
|
||||
|
||||
private static TypeSpec.Builder defineAttributeClass(String pkg, Node attribute) {
|
||||
ClassName name = ClassName.get(pkg, "I" + capitalize(attribute.name));
|
||||
return TypeSpec.interfaceBuilder(name)
|
||||
.addSuperinterface(ParameterizedTypeName.get(INSTANCE, TypeVariableName.get("T")))
|
||||
.addTypeVariable(TypeVariableName.get("T", ParameterizedTypeName.get(TAG, TypeVariableName.get("T"))))
|
||||
.addModifiers(Modifier.PUBLIC);
|
||||
}
|
||||
|
||||
private static void defineBooleanAttributeMethods(Node attribute, TypeSpec.Builder type) {
|
||||
MethodSpec with = MethodSpec.methodBuilder(methodName("is", attribute.name))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
|
||||
.addStatement("return self().attr(\"" + attribute.name + "\")")
|
||||
.returns(TypeVariableName.get("T"))
|
||||
.build();
|
||||
|
||||
MethodSpec withCond = MethodSpec.methodBuilder(methodName("withCond", attribute.name))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
|
||||
.addParameter(TypeName.BOOLEAN, "enable", Modifier.FINAL)
|
||||
.addStatement("return enable ? self().attr(\"" + attribute.name + "\") : self()")
|
||||
.returns(TypeVariableName.get("T"))
|
||||
.build();
|
||||
|
||||
type.addMethod(with);
|
||||
type.addMethod(withCond);
|
||||
}
|
||||
|
||||
private static void defineOnOffAttributeMethods(Node attribute, TypeSpec.Builder type) {
|
||||
MethodSpec with = MethodSpec.methodBuilder(methodName("is", attribute.name))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
|
||||
.addStatement("return self().attr(\"" + attribute.name + "\", \"on\")")
|
||||
.returns(TypeVariableName.get("T"))
|
||||
.build();
|
||||
|
||||
MethodSpec withCond = MethodSpec.methodBuilder(methodName("withCond", attribute.name))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
|
||||
.addParameter(TypeName.BOOLEAN, "enable", Modifier.FINAL)
|
||||
.addStatement("return enable ? self().attr(\"" + attribute.name + "\", \"on\") : self()")
|
||||
.returns(TypeVariableName.get("T"))
|
||||
.build();
|
||||
|
||||
type.addMethod(with);
|
||||
type.addMethod(withCond);
|
||||
}
|
||||
|
||||
private static void defineStringAttributeMethods(Node attribute, TypeSpec.Builder type) {
|
||||
MethodSpec with = MethodSpec.methodBuilder(methodName("with", attribute.name))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
|
||||
.addParameter(String.class, parameter(attribute), Modifier.FINAL)
|
||||
.addStatement("return self().attr(\"" + attribute.name + "\", " + parameter(attribute) + ")")
|
||||
.returns(TypeVariableName.get("T"))
|
||||
.build();
|
||||
|
||||
MethodSpec withCond = MethodSpec.methodBuilder(methodName("withCond", attribute.name))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
|
||||
.addParameter(TypeName.BOOLEAN, "enable", Modifier.FINAL)
|
||||
.addParameter(String.class, parameter(attribute), Modifier.FINAL)
|
||||
.addStatement("return enable ? self().attr(\"" + attribute.name + "\", " + parameter(attribute) + ") : self()")
|
||||
.returns(TypeVariableName.get("T"))
|
||||
.build();
|
||||
|
||||
type.addMethod(with);
|
||||
type.addMethod(withCond);
|
||||
}
|
||||
|
||||
private static String parameter(Node attribute) {
|
||||
return attribute.name + "_";
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.j2html.codegen;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public final class GeneratorUtil {
|
||||
|
||||
public static final void deleteAllFilesInDir(final Path dir) throws IOException {
|
||||
for(final File file : dir.toFile().listFiles()){
|
||||
System.out.println("deleting " + file.toPath());
|
||||
Files.delete(file.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.j2html.codegen;
|
||||
|
||||
import org.apache.maven.plugin.AbstractMojo;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoFailureException;
|
||||
import org.apache.maven.plugins.annotations.LifecyclePhase;
|
||||
import org.apache.maven.plugins.annotations.Mojo;
|
||||
import org.apache.maven.plugins.annotations.Parameter;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@Mojo(name = "generate-source-files", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
|
||||
public class J2htmlCodeGeneratorMojo extends AbstractMojo {
|
||||
|
||||
@Parameter(defaultValue = "${project}", required = true, readonly = true)
|
||||
MavenProject project;
|
||||
|
||||
@Parameter(property = "modelFile", required = true)
|
||||
String modelFile;
|
||||
|
||||
@Parameter(property = "attributePackage", required = true)
|
||||
String attributePackage;
|
||||
|
||||
@Parameter(property = "tagPackage", required = true)
|
||||
String tagPackage;
|
||||
|
||||
@Override
|
||||
public void execute() throws MojoExecutionException, MojoFailureException {
|
||||
getLog().debug("Model File: " + modelFile);
|
||||
getLog().debug("Attribute Package: " + attributePackage);
|
||||
getLog().debug("Tag Package: " + tagPackage);
|
||||
|
||||
String outputDirectory = project.getBuild().getDirectory() + "/generated-sources/j2html-codegen";
|
||||
project.addCompileSourceRoot(outputDirectory);
|
||||
getLog().debug("Generating J2Html sources in: " + outputDirectory);
|
||||
|
||||
String definitions;
|
||||
try {
|
||||
definitions = new String(Files.readAllBytes(Paths.get(modelFile)));
|
||||
} catch (IOException e) {
|
||||
throw new MojoFailureException("Unable to locate model file: " + modelFile, e);
|
||||
}
|
||||
|
||||
Model model = new Model();
|
||||
try {
|
||||
Parser.parse(definitions, model);
|
||||
}catch (RuntimeException e){
|
||||
throw new MojoFailureException("Unable to parse model file.", e);
|
||||
}
|
||||
|
||||
try {
|
||||
Generator.generate(
|
||||
Paths.get(outputDirectory).toAbsolutePath(),
|
||||
attributePackage,
|
||||
tagPackage,
|
||||
model
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new MojoFailureException("Failed to generate source files.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
171
j2html-codegen/src/main/java/com/j2html/codegen/Model.java
Normal file
171
j2html-codegen/src/main/java/com/j2html/codegen/Model.java
Normal file
@@ -0,0 +1,171 @@
|
||||
package com.j2html.codegen;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.j2html.codegen.Model.Metadata.ON_OFF;
|
||||
import static com.j2html.codegen.Model.Metadata.SELF_CLOSING;
|
||||
import static com.j2html.codegen.Model.Node.Type.*;
|
||||
|
||||
public class Model implements Parser.Listener {
|
||||
|
||||
private Map<String, Node> elements;
|
||||
private Map<String, Node> attributes;
|
||||
|
||||
public Model() {
|
||||
elements = new LinkedHashMap<>();
|
||||
attributes = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public Collection<Node> elements(){
|
||||
return elements.values();
|
||||
}
|
||||
|
||||
public Collection<Node> attributes(){
|
||||
return attributes.values();
|
||||
}
|
||||
|
||||
public Node addElement(String name) {
|
||||
return add(ELEMENT, name, elements);
|
||||
}
|
||||
|
||||
public Node addBooleanAttribute(String name) {
|
||||
return add(BOOLEAN, name, attributes);
|
||||
}
|
||||
|
||||
public Node addStringAttribute(String name) {
|
||||
return add(STRING, name, attributes);
|
||||
}
|
||||
|
||||
public Node element(String name) {
|
||||
if (!elements.containsKey(name)) {
|
||||
throw new NodeDoesNotExist(name);
|
||||
}
|
||||
return elements.get(name);
|
||||
}
|
||||
|
||||
public Node attribute(String name) {
|
||||
if (!attributes.containsKey(name)) {
|
||||
throw new NodeDoesNotExist(name);
|
||||
}
|
||||
return attributes.get(name);
|
||||
}
|
||||
|
||||
private Node add(Node.Type type, String name, Map<String, Node> nodes) {
|
||||
if (nodes.containsKey(name)) {
|
||||
throw new NodeAlreadyExists(name);
|
||||
}
|
||||
|
||||
Node node = new Node(type, name);
|
||||
nodes.put(name, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineCommented(int line, String txt) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementDefined(int line, String name) {
|
||||
attempt(() -> addElement(name), line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emptyElementDefined(int line, String name) {
|
||||
attempt(() -> addElement(name).annotate(SELF_CLOSING), line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void booleanDefined(int line, String name) {
|
||||
attempt(() -> addBooleanAttribute(name), line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOffDefined(int line, String name) {
|
||||
attempt(() -> addBooleanAttribute(name).annotate(ON_OFF), line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stringDefined(int line, String name) {
|
||||
attempt(() -> addStringAttribute(name), line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributeDefined(int line, String element, String name) {
|
||||
attempt(() -> element(element).addChild(attribute(name)), line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidLine(int line, String txt) {
|
||||
throw new RuntimeException("Invalid line [" + line + "]: " + txt);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface Unsafe {
|
||||
void call() throws RuntimeException;
|
||||
}
|
||||
|
||||
private void attempt(Unsafe operation, int line) {
|
||||
try {
|
||||
operation.call();
|
||||
} catch (RuntimeException e) {
|
||||
throw new InvalidModel(e, line);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Node {
|
||||
enum Type {
|
||||
ELEMENT,
|
||||
BOOLEAN,
|
||||
STRING
|
||||
}
|
||||
|
||||
public final Type type;
|
||||
public final String name;
|
||||
public final List<Metadata> metadata;
|
||||
public final List<Node> children;
|
||||
|
||||
private Node(Type type, String name) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.metadata = new ArrayList<>();
|
||||
this.children = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void annotate(Metadata meta) {
|
||||
metadata.add(meta);
|
||||
}
|
||||
|
||||
public void addChild(Node node) {
|
||||
children.add(node);
|
||||
}
|
||||
|
||||
public boolean is(Metadata annotation){
|
||||
return metadata.contains(annotation);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Metadata {
|
||||
SELF_CLOSING,
|
||||
ON_OFF,
|
||||
OBSOLETE
|
||||
}
|
||||
|
||||
public static class InvalidModel extends RuntimeException {
|
||||
public InvalidModel(Exception cause, int line) {
|
||||
super(cause.getMessage() + ". At line " + line, cause);
|
||||
}
|
||||
}
|
||||
|
||||
public static class NodeAlreadyExists extends RuntimeException {
|
||||
public NodeAlreadyExists(String name) {
|
||||
super("Node already exists: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
public static class NodeDoesNotExist extends RuntimeException {
|
||||
public NodeDoesNotExist(String name) {
|
||||
super("Node does not exist: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
91
j2html-codegen/src/main/java/com/j2html/codegen/Parser.java
Normal file
91
j2html-codegen/src/main/java/com/j2html/codegen/Parser.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package com.j2html.codegen;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Parser {
|
||||
|
||||
private static final Pattern EMPTY_LINE_PATTERN = Pattern.compile("\\s*");
|
||||
private static final Pattern COMMENT_PATTERN = Pattern.compile("#.*");
|
||||
private static final Pattern NODE_PATTERN = Pattern.compile("(?<type>ELEMENT|EMPTY-ELEMENT|BOOLEAN|ONOFF|STRING)\\[(?<name>\\S+)\\]");
|
||||
private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("ATTRIBUTE\\[(?<element>\\S+):(?<name>\\S+)\\]");
|
||||
|
||||
public interface Listener {
|
||||
void lineCommented(int line, String txt);
|
||||
|
||||
void elementDefined(int line, String name);
|
||||
|
||||
void emptyElementDefined(int line, String name);
|
||||
|
||||
void booleanDefined(int line, String name);
|
||||
|
||||
void onOffDefined(int line, String name);
|
||||
|
||||
void stringDefined(int line, String name);
|
||||
|
||||
void attributeDefined(int line, String element, String name);
|
||||
|
||||
void invalidLine(int line, String txt);
|
||||
}
|
||||
|
||||
public static void parse(String txt, Listener listener) {
|
||||
String[] lines = txt.split("[\r\n]+");
|
||||
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
int number = i + 1;
|
||||
String line = lines[i];
|
||||
|
||||
if (match(EMPTY_LINE_PATTERN, line)) continue;
|
||||
|
||||
if (match(COMMENT_PATTERN, line, matcher -> {
|
||||
listener.lineCommented(number, line);
|
||||
})) continue;
|
||||
|
||||
if (match(NODE_PATTERN, line, matcher -> {
|
||||
String type = matcher.group("type");
|
||||
String name = matcher.group("name");
|
||||
switch (type) {
|
||||
case "ELEMENT":
|
||||
listener.elementDefined(number, name);
|
||||
break;
|
||||
case "EMPTY-ELEMENT":
|
||||
listener.emptyElementDefined(number, name);
|
||||
break;
|
||||
case "BOOLEAN":
|
||||
listener.booleanDefined(number, name);
|
||||
break;
|
||||
case "ONOFF":
|
||||
listener.onOffDefined(number, name);
|
||||
break;
|
||||
case "STRING":
|
||||
listener.stringDefined(number, name);
|
||||
break;
|
||||
}
|
||||
})) continue;
|
||||
|
||||
if (match(ATTRIBUTE_PATTERN, line, matcher -> {
|
||||
listener.attributeDefined(
|
||||
number,
|
||||
matcher.group("element"),
|
||||
matcher.group("name")
|
||||
);
|
||||
})) continue;
|
||||
|
||||
listener.invalidLine(number, line);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean match(Pattern pattern, String txt) {
|
||||
return pattern.matcher(txt).matches();
|
||||
}
|
||||
|
||||
private static boolean match(Pattern pattern, String txt, Consumer<Matcher> onMatch) {
|
||||
Matcher matcher = pattern.matcher(txt);
|
||||
if (matcher.matches()) {
|
||||
onMatch.accept(matcher);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package com.j2html.codegen.generators;
|
||||
|
||||
import com.j2html.codegen.GeneratorUtil;
|
||||
import com.j2html.codegen.model.AttrD;
|
||||
import com.j2html.codegen.model.AttributesList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class AttributeInterfaceCodeGenerator {
|
||||
|
||||
private static final String relPath = "tags/attributes/";
|
||||
|
||||
public static void generate(final Path absPath, final boolean delete) throws IOException {
|
||||
|
||||
//delete all files in the directory for fresh generation
|
||||
final Path dir = Paths.get(absPath.toString(),relPath);
|
||||
GeneratorUtil.deleteAllFilesInDir(dir);
|
||||
|
||||
for (final AttrD attr : AttributesList.attributesDescriptive()) {
|
||||
final Path path = makePath(attr.attr, absPath);
|
||||
final String interfaceName = interfaceNameFromAttribute(attr.attr)+"<T extends Tag<T>>";
|
||||
/*
|
||||
IFormAction<T extends Tag<T>> extends IInstance<T>
|
||||
|
||||
default T withFormAction(String formAction){
|
||||
return self().attr("formaction", formAction);
|
||||
}
|
||||
*/
|
||||
final String interfaceStr = getInterfaceTemplate(
|
||||
interfaceName,
|
||||
Optional.of("IInstance<T>"),
|
||||
Arrays.asList("j2html.tags.Tag","j2html.tags.IInstance"),
|
||||
interfaceNameFromAttribute(attr.attr).substring(1),
|
||||
attr
|
||||
);
|
||||
|
||||
if (!delete) {
|
||||
System.out.println("writing to "+path);
|
||||
Files.write(path, interfaceStr.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String getPackage(){
|
||||
return "package j2html.tags.attributes;\n";
|
||||
}
|
||||
|
||||
private static String makeReturnTypeAndMethodName(final String name){
|
||||
return "default "+ "T "+name;
|
||||
}
|
||||
|
||||
private static String getInterfaceTemplate(
|
||||
final String interfaceName,
|
||||
final Optional<String> optExtends,
|
||||
final List<String> imports,
|
||||
final String interfaceNameSimple,
|
||||
final AttrD attrD
|
||||
){
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(getPackage());
|
||||
sb.append("\n");
|
||||
|
||||
for(String importName : imports){
|
||||
sb.append("import ").append(importName).append(";\n");
|
||||
}
|
||||
sb.append("\n");
|
||||
sb.append("public interface ")
|
||||
.append(interfaceName);
|
||||
|
||||
optExtends.ifPresent(ext -> sb.append(" extends ").append(ext).append(" "));
|
||||
|
||||
sb.append(" {\n");
|
||||
|
||||
//interface contents
|
||||
/*
|
||||
IFormAction<T extends Tag> extends IInstance<T>
|
||||
|
||||
default T withFormAction(String formAction){
|
||||
return self().attr("formaction", formAction);
|
||||
}
|
||||
*/
|
||||
//IMPORTANT: '_' added as suffix to mitigate problems
|
||||
//where attributes are java keywords. Just to make it consistent and avoid special cases.
|
||||
final String attrName = interfaceNameSimple.toLowerCase();
|
||||
final String paramName = attrName+"_";
|
||||
|
||||
//depending on if the attribute has an argument or not,
|
||||
//generate methods according to the convention in Tag.java
|
||||
// arg -> with$ATTR(arg), withCond$ATTR(condition, arg)
|
||||
// no arg -> is$ATTR(), withCond$ATTR(condition)
|
||||
|
||||
//append the 'with$ATTR' method
|
||||
writeAttributeMethod(interfaceNameSimple, attrD, sb, attrName, paramName);
|
||||
writeAttributeMethodCond(interfaceNameSimple, attrD, sb, attrName, paramName);
|
||||
|
||||
sb.append("}\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void addAttributeNoArg(final StringBuilder sb, final String attrName){
|
||||
//generate the code to add an attribute without an argument
|
||||
|
||||
//there are some special attributes
|
||||
//which do take an argument, but where the argument
|
||||
//is boolean (meaning on/off, yes/no and the like)
|
||||
sb.append("self().attr(\"");
|
||||
if (attrName.equals("autocomplete")){
|
||||
sb.append(attrName).append("\",\"on\"");
|
||||
} else {
|
||||
sb.append(attrName).append("\"");
|
||||
}
|
||||
sb.append(");\n");
|
||||
}
|
||||
|
||||
private static void writeAttributeMethodCond(String interfaceNameSimple, AttrD attrD, StringBuilder sb, String attrName, String paramName) {
|
||||
|
||||
sb.append(makeReturnTypeAndMethodName("withCond"+interfaceNameSimple));
|
||||
|
||||
if(attrD.hasArgument){
|
||||
//add a variant where you can specify the argument
|
||||
|
||||
sb.append("(final boolean enable, final String ").append(paramName).append(") {");
|
||||
|
||||
sb.append("if (enable){\n");
|
||||
sb.append("self().attr(\"").append(attrName).append("\", ").append(paramName).append(");\n");
|
||||
sb.append("}\n");
|
||||
|
||||
sb.append("return self();\n");
|
||||
}else{
|
||||
//add a variant where you can toggle the attribute
|
||||
|
||||
sb.append("(final boolean enable) {");
|
||||
sb.append("if (enable){\n");
|
||||
addAttributeNoArg(sb, attrName);
|
||||
sb.append("}\n");
|
||||
sb.append("return self();\n");
|
||||
}
|
||||
sb.append("}\n");
|
||||
}
|
||||
|
||||
private static void writeAttributeMethod(String interfaceNameSimple, AttrD attrD, StringBuilder sb, String attrName, String paramName) {
|
||||
|
||||
sb.append(makeReturnTypeAndMethodName(
|
||||
((attrD.hasArgument)?"with":"is")+interfaceNameSimple)
|
||||
);
|
||||
|
||||
if(attrD.hasArgument){
|
||||
//add a variant where you can specify the argument
|
||||
|
||||
sb.append("(final String ").append(paramName).append(") {")
|
||||
|
||||
.append("return self().attr(\"").append(attrName).append("\", ").append(paramName).append(");\n");
|
||||
}else{
|
||||
//add a variant where you can toggle the attribute
|
||||
|
||||
sb.append("() {");
|
||||
|
||||
addAttributeNoArg(sb, attrName);
|
||||
|
||||
sb.append("return self();\n");
|
||||
}
|
||||
sb.append("}\n");
|
||||
}
|
||||
|
||||
public static String interfaceNameFromAttribute(String attribute){
|
||||
String res = attribute.substring(0,1).toUpperCase()+attribute.substring(1);
|
||||
return "I" + res;
|
||||
}
|
||||
|
||||
private static Path makePath(String tagLowerCase, final Path absPath){
|
||||
final String filename = interfaceNameFromAttribute(tagLowerCase)+".java";
|
||||
return Paths.get(absPath.toString(),relPath,filename);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package com.j2html.codegen.generators;
|
||||
|
||||
|
||||
import com.j2html.codegen.GeneratorUtil;
|
||||
import com.j2html.codegen.model.AttributesList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.j2html.codegen.generators.TagCreatorCodeGenerator.containerTags;
|
||||
import static com.j2html.codegen.generators.TagCreatorCodeGenerator.emptyTags;
|
||||
|
||||
public final class SpecializedTagClassCodeGenerator {
|
||||
|
||||
private static final String relPath = "tags/specialized";
|
||||
|
||||
public static void generate(final Path absPath, final boolean delete) throws IOException {
|
||||
|
||||
//delete all files in the directory for fresh generation
|
||||
final Path dir = Paths.get(absPath.toString(),relPath);
|
||||
GeneratorUtil.deleteAllFilesInDir(dir);
|
||||
|
||||
//the delete argument serves to give the possibility
|
||||
//to delete the classes that were written before
|
||||
System.out.println("// EmptyTags, generated in " + SpecializedTagClassCodeGenerator.class);
|
||||
|
||||
for (final String tag : emptyTags()) {
|
||||
final String className = classNameFromTag(tag);
|
||||
final Path path = makePath(absPath,tag);
|
||||
|
||||
final List<String> interfaceNames = getInterfaceNamesForTag(tag);
|
||||
|
||||
final String classString =
|
||||
getClassTemplate(
|
||||
className,
|
||||
Optional.of("EmptyTag<"+className+">"),
|
||||
Arrays.asList(
|
||||
"j2html.tags.EmptyTag",
|
||||
"j2html.tags.attributes.*"
|
||||
),
|
||||
tag,
|
||||
interfaceNames
|
||||
);
|
||||
|
||||
/*
|
||||
public InputTag() {
|
||||
super("input");
|
||||
}
|
||||
*/
|
||||
|
||||
if(!delete){
|
||||
System.out.println("writing to "+path);
|
||||
Files.write(path, classString.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("// ContainerTags, generated in " + SpecializedTagClassCodeGenerator.class);
|
||||
|
||||
for (final String tag : containerTags()) {
|
||||
final Path path = makePath(absPath, tag);
|
||||
final String className = classNameFromTag(tag);
|
||||
|
||||
final List<String> interfaceNames = getInterfaceNamesForTag(tag);
|
||||
|
||||
final String classString =
|
||||
getClassTemplate(
|
||||
className,
|
||||
Optional.of("ContainerTag<"+className+">"),
|
||||
Arrays.asList(
|
||||
"j2html.tags.ContainerTag",
|
||||
"j2html.tags.attributes.*"
|
||||
),
|
||||
tag,
|
||||
interfaceNames
|
||||
);
|
||||
|
||||
if(delete){
|
||||
if(Files.exists(path)) {
|
||||
System.out.println("deleting " + path);
|
||||
Files.delete(path);
|
||||
}
|
||||
}else {
|
||||
System.out.println("writing to "+path);
|
||||
Files.write(path, classString.getBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
public static String classNameFromTag(String tageNameLowerCase){
|
||||
String res = tageNameLowerCase.substring(0,1).toUpperCase()+tageNameLowerCase.substring(1);
|
||||
return res + "Tag";
|
||||
}
|
||||
|
||||
private static Path makePath(final Path absPath, String tagLowerCase){
|
||||
final String filename = classNameFromTag(tagLowerCase)+".java";
|
||||
return Paths.get(absPath.toString(),relPath,filename);
|
||||
}
|
||||
|
||||
private static String getPackage(){
|
||||
return "package j2html.tags.specialized;\n";
|
||||
}
|
||||
|
||||
private static String getClassTemplate(
|
||||
final String className,
|
||||
final Optional<String> optExtends,
|
||||
final List<String> imports,
|
||||
final String tag,
|
||||
final List<String> interfaces
|
||||
){
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(getPackage());
|
||||
sb.append("\n");
|
||||
|
||||
for(String importName : imports){
|
||||
sb.append("import ").append(importName).append(";\n");
|
||||
}
|
||||
sb.append("\n");
|
||||
sb.append("public final class ")
|
||||
.append(className)
|
||||
.append(" ");
|
||||
|
||||
optExtends.ifPresent(ext -> sb.append("extends ").append(ext).append(" "));
|
||||
|
||||
//add the 'implements' clause
|
||||
if(!interfaces.isEmpty()) {
|
||||
sb.append("\n");
|
||||
sb.append("implements ");
|
||||
|
||||
final List<String> genericInterfaceNames
|
||||
= interfaces.stream().map(iName -> iName+"<"+className+">")
|
||||
.collect(Collectors.toList());
|
||||
sb.append(
|
||||
String.join(",", genericInterfaceNames)
|
||||
);
|
||||
}
|
||||
|
||||
sb.append(" {\n");
|
||||
|
||||
//class contents
|
||||
sb.append("public ")
|
||||
.append(className)
|
||||
.append("() {")
|
||||
.append("super(\"").append(tag).append("\");")
|
||||
.append("}\n");
|
||||
|
||||
sb.append("}\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static List<String> getInterfaceNamesForTag(final String tagNameLowercase){
|
||||
return AttributesList.getCustomAttributesForHtmlTag(tagNameLowercase)
|
||||
.stream()
|
||||
.map(
|
||||
AttributeInterfaceCodeGenerator::interfaceNameFromAttribute
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package com.j2html.codegen.generators;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public final class TagCreatorCodeGenerator {
|
||||
|
||||
public static void print() {
|
||||
|
||||
System.out.println("// EmptyTags, generated in " + TagCreatorCodeGenerator.class);
|
||||
|
||||
for (String tag : emptyTags()) {
|
||||
final String className = SpecializedTagClassCodeGenerator.classNameFromTag(tag);
|
||||
final String publicstaticTypeMethod = "public static "+className+" "+tag+" ";
|
||||
final String castReturn = " return ("+className+") ";
|
||||
final String construct = " new "+className+"()";
|
||||
|
||||
String emptyA1 = publicstaticTypeMethod + "()";
|
||||
String emptyA2 = "{ return "+construct+"; }";
|
||||
// Attr shorthands
|
||||
String emptyB1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr)";
|
||||
String emptyB2 = "{ "+castReturn+" Attr.addTo("+construct+", shortAttr); }";
|
||||
// Print
|
||||
System.out.println(String.format("%-80s%1s", emptyA1, emptyA2));
|
||||
System.out.println(String.format("%-80s%1s", emptyB1, emptyB2));
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.println("// ContainerTags, generated in " + TagCreatorCodeGenerator.class);
|
||||
|
||||
for (String tag : containerTags()) {
|
||||
final String className = SpecializedTagClassCodeGenerator.classNameFromTag(tag);
|
||||
final String publicstaticTypeMethod = "public static "+className+" "+tag+" ";
|
||||
final String castReturn = " return ("+className+") ";
|
||||
final String construct = " new "+className+"()";
|
||||
|
||||
String containerA1 = publicstaticTypeMethod+ "()";
|
||||
String containerA2 = "{ "+castReturn + construct + "; }";
|
||||
|
||||
String containerB1 = publicstaticTypeMethod + "(String text)";
|
||||
String containerB2 = "{ "+castReturn + construct + ".withText(text); }";
|
||||
|
||||
String containerC1 = publicstaticTypeMethod + "(DomContent... dc)";
|
||||
String containerC2 = "{ "+castReturn + construct+".with(dc); }";
|
||||
// Attr shorthands
|
||||
String containerD1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr)";
|
||||
String containerD2 = "{ "+castReturn+" Attr.addTo("+construct+", shortAttr); }";
|
||||
|
||||
String containerE1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr, String text)";
|
||||
String containerE2 = "{ "+castReturn+" Attr.addTo("+construct+".withText(text), shortAttr); }";
|
||||
|
||||
String containerF1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr, DomContent... dc)";
|
||||
String containerF2 = "{ "+castReturn+" Attr.addTo("+construct+".with(dc), shortAttr); }";
|
||||
// Print
|
||||
System.out.println(String.format("%-80s%1s", containerA1, containerA2));
|
||||
System.out.println(String.format("%-80s%1s", containerB1, containerB2));
|
||||
System.out.println(String.format("%-80s%1s", containerC1, containerC2));
|
||||
System.out.println(String.format("%-80s%1s", containerD1, containerD2));
|
||||
System.out.println(String.format("%-80s%1s", containerE1, containerE2));
|
||||
System.out.println(String.format("%-80s%1s", containerF1, containerF2));
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
|
||||
// This is a method that contains all ContainerTags, there is nothing below it
|
||||
public static List<String> emptyTags() {
|
||||
return Arrays.asList(
|
||||
"area",
|
||||
"base",
|
||||
"br",
|
||||
"col",
|
||||
//"!DOCTYPE html",
|
||||
"embed",
|
||||
"hr",
|
||||
"img",
|
||||
"input",
|
||||
"keygen",
|
||||
"link",
|
||||
"meta",
|
||||
"param",
|
||||
"source",
|
||||
"track",
|
||||
"wbr"
|
||||
);
|
||||
}
|
||||
|
||||
public static List<String> containerTags() {
|
||||
return Arrays.asList(
|
||||
"a",
|
||||
"abbr",
|
||||
"address",
|
||||
"article",
|
||||
"aside",
|
||||
"audio",
|
||||
"b",
|
||||
"bdi",
|
||||
"bdo",
|
||||
"blockquote",
|
||||
"body",
|
||||
"button",
|
||||
"canvas",
|
||||
"caption",
|
||||
"cite",
|
||||
"code",
|
||||
"colgroup",
|
||||
"data",
|
||||
"datalist",
|
||||
"dd",
|
||||
"del",
|
||||
"details",
|
||||
"dfn",
|
||||
"dialog",
|
||||
"div",
|
||||
"dl",
|
||||
"dt",
|
||||
"em",
|
||||
"fieldset",
|
||||
"figcaption",
|
||||
"figure",
|
||||
"footer",
|
||||
"form",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"head",
|
||||
"header",
|
||||
"html",
|
||||
"i",
|
||||
"iframe",
|
||||
"ins",
|
||||
"kbd",
|
||||
"label",
|
||||
"legend",
|
||||
"li",
|
||||
"main",
|
||||
"map",
|
||||
"mark",
|
||||
"menu",
|
||||
"menuitem",
|
||||
"meter",
|
||||
"nav",
|
||||
"noscript",
|
||||
"object",
|
||||
"ol",
|
||||
"optgroup",
|
||||
"option",
|
||||
"output",
|
||||
"p",
|
||||
"picture",
|
||||
"pre",
|
||||
"progress",
|
||||
"q",
|
||||
"rp",
|
||||
"rt",
|
||||
"ruby",
|
||||
"s",
|
||||
"samp",
|
||||
"script",
|
||||
"section",
|
||||
"select",
|
||||
"slot",
|
||||
"small",
|
||||
"span",
|
||||
"strong",
|
||||
"style",
|
||||
"sub",
|
||||
"summary",
|
||||
"sup",
|
||||
"table",
|
||||
"tbody",
|
||||
"td",
|
||||
"template",
|
||||
"textarea",
|
||||
"tfoot",
|
||||
"th",
|
||||
"thead",
|
||||
"time",
|
||||
"title",
|
||||
"tr",
|
||||
"u",
|
||||
"ul",
|
||||
"var",
|
||||
"video"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.j2html.codegen.generators;
|
||||
|
||||
import com.j2html.codegen.wattsi.AttributeDefinition;
|
||||
import com.j2html.codegen.wattsi.ElementDefinition;
|
||||
import com.j2html.codegen.wattsi.WattsiSource;
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.MethodSpec;
|
||||
import com.squareup.javapoet.TypeSpec;
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.j2html.codegen.model;
|
||||
|
||||
public final class AttrD {
|
||||
//attribute descriptor
|
||||
|
||||
public final String attr;
|
||||
|
||||
public final boolean hasArgument;
|
||||
|
||||
//the html tags that this attribute can be used on
|
||||
public final String[] tags;
|
||||
|
||||
public AttrD(final String attr, boolean hasArgument){
|
||||
this.attr = attr;
|
||||
this.hasArgument = hasArgument;
|
||||
this.tags = new String[]{};
|
||||
}
|
||||
|
||||
public AttrD(final String attr, boolean hasArgument, final String... tags) {
|
||||
this.attr = attr;
|
||||
this.hasArgument = hasArgument;
|
||||
this.tags = tags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package com.j2html.codegen.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public final class AttributesList {
|
||||
|
||||
//https://www.w3schools.com/tags/ref_attributes.asp
|
||||
|
||||
public static List<String> getCustomAttributesForHtmlTag(final String tagLowercase){
|
||||
|
||||
final List<String> attrs = new ArrayList<>();
|
||||
for(AttrD attrD : attributesDescriptive()){
|
||||
if(
|
||||
Arrays.asList(attrD.tags).contains(tagLowercase)
|
||||
){
|
||||
attrs.add(attrD.attr);
|
||||
}
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
|
||||
public static List<AttrD> attributesDescriptive() {
|
||||
return Arrays.asList(
|
||||
new AttrD("accept", true, "input"),
|
||||
//new AttrD("accept-charset","form"), //contains dashes, TODO
|
||||
//new AttrD("accesskey"), //global attribute
|
||||
new AttrD("action", true, "form"),
|
||||
//"align", not supported in HTML5
|
||||
new AttrD("alt", true, "area","img","input"),
|
||||
new AttrD("async", false, "script"),
|
||||
new AttrD("autocomplete", false, "form","input"),
|
||||
new AttrD("autofocus", false, "button","input","select","textarea"),
|
||||
new AttrD("autoplay", false, "audio","video"),
|
||||
//"bgcolor", not supported in HTMTL5
|
||||
//"border", not supported in HTML5
|
||||
new AttrD("charset", true, "meta","script"),
|
||||
new AttrD("checked", false, "input"),
|
||||
new AttrD("cite", true, "blockquote","del","ins","q"),
|
||||
//"class" already implemented in Tag.java // global attribute
|
||||
new AttrD("cols", true, "textarea"),
|
||||
new AttrD("colspan", true, "td","th"),
|
||||
new AttrD("content", true, "meta"),
|
||||
//"contenteditable" global attribute, should be in Tag.java
|
||||
new AttrD("controls", false, "audio","video"),
|
||||
new AttrD("coords", true, "area"),
|
||||
new AttrD("data", true, "object"),
|
||||
new AttrD("datetime", true, "del","ins","time"),
|
||||
new AttrD("default", false, "track"),
|
||||
new AttrD("defer", false, "script"),
|
||||
//new AttrD("dir"), //global attribute
|
||||
new AttrD("dirname", true, "input","textarea"),
|
||||
new AttrD("disabled",false, "button","fieldset","input","optgroup","option","select","textarea"),
|
||||
new AttrD("download",false, "a","area"),
|
||||
//new AttrD("draggable") global attribute, should be in Tag.java
|
||||
new AttrD("enctype", true, "form"),
|
||||
new AttrD("for", true, "label","output"),
|
||||
new AttrD("form", true, "button","fieldset","input","label","meter","object","output","select","textarea"),
|
||||
new AttrD("formaction", true, "button","input"),
|
||||
new AttrD("headers", true, "td","th"),
|
||||
new AttrD("height", true, "canvas","embed","iframe","img","input","object","video"),
|
||||
//new AttrD("hidden"), global attribute
|
||||
new AttrD("high", true, "meter"),
|
||||
new AttrD("href", true, "a","area","base","link"),
|
||||
new AttrD("hreflang", true, "a","area","link"),
|
||||
//"http-equiv", //TODO: '-' is problematic in code generation
|
||||
//"id" global attribute, should be in Tag.java
|
||||
new AttrD("ismap", false, "img"),
|
||||
new AttrD("kind", true, "track"),
|
||||
new AttrD("label", true, "track","option","optgroup"),
|
||||
//"lang" global attribute, should be in Tag.java
|
||||
new AttrD("list", true, "input"),
|
||||
new AttrD("loop", false, "audio","video"),
|
||||
new AttrD("low", true, "meter"),
|
||||
new AttrD("max", true, "input","meter","progress"),
|
||||
new AttrD("maxlength", true, "input","textarea"),
|
||||
new AttrD("media", true, "a","area","link","source","style"),
|
||||
new AttrD("method", true, "form"),
|
||||
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","slot","textarea"),
|
||||
new AttrD("novalidate", false, "form"),
|
||||
new AttrD("onabort", true, "audio","embed","img","object","video"),
|
||||
new AttrD("onafterprint", true, "body"),
|
||||
new AttrD("onbeforeprint", true, "body"),
|
||||
new AttrD("onbeforeunload", true, "body"),
|
||||
//new AttrD("onblur"), global attribute
|
||||
new AttrD("oncanplay", true, "audio","embed","object","video"),
|
||||
new AttrD("oncanplaythrough", true, "audio","video"),
|
||||
/* a bunch of event attributes that are on all visible elements (so should be in Tag.java)
|
||||
"onchange",
|
||||
"onclick",
|
||||
"oncontextmenu",
|
||||
"oncopy",
|
||||
*/
|
||||
new AttrD("oncuechange", true, "track"),
|
||||
/*
|
||||
"oncut",
|
||||
...
|
||||
"ondrop",
|
||||
*/
|
||||
new AttrD("ondurationchange", true, "audio","video"),
|
||||
new AttrD("onemptied", true, "audio","video"),
|
||||
new AttrD("onended", true, "audio","video"),
|
||||
new AttrD("onerror", true, "audio","body","embed","img","object","script","style","video"),
|
||||
//new AttrD("onfocus"),// global attribute
|
||||
new AttrD("onhashchange", true, "body"),
|
||||
// ... a bunch of event attributes visible on all elements
|
||||
new AttrD("onload", true, "body","iframe","img","input","link","script","style"),
|
||||
new AttrD("onloadeddata", true, "audio","video"),
|
||||
new AttrD("onloadedmetadata", true, "audio","video"),
|
||||
new AttrD("onloadstart", true, "audio","video"),
|
||||
// ... a bunch of event attributes visible on all elements
|
||||
new AttrD("onoffline", true, "body"),
|
||||
new AttrD("ononline", true, "body"),
|
||||
new AttrD("onpagehide", true, "body"),
|
||||
new AttrD("onpageshow", true, "body"),
|
||||
//new AttrD("onpaste"),// global attribute
|
||||
new AttrD("onpause", true, "audio","video"),
|
||||
new AttrD("onplay", true, "audio","video"),
|
||||
new AttrD("onplaying", true, "audio","video"),
|
||||
new AttrD("onpopstate", true, "body"),
|
||||
new AttrD("onprogress", true, "audio","video"),
|
||||
new AttrD("onratechange", true, "audio","video"),
|
||||
new AttrD("onreset", true, "form"),
|
||||
new AttrD("onresize", true, "body"),
|
||||
//new AttrD("onscroll"), //global attribute
|
||||
new AttrD("onsearch", true, "input"),
|
||||
new AttrD("onseeked", true, "audio","video"),
|
||||
new AttrD("onseeking", true, "audio","video"),
|
||||
//new AttrD("onselect"), //global attribute
|
||||
new AttrD("onstalled", true, "audio","video"),
|
||||
new AttrD("onstorage", true, "body"),
|
||||
new AttrD("onsubmit", true, "form"),
|
||||
new AttrD("onsuspend", true, "audio","video"),
|
||||
new AttrD("ontimeupdate", true, "audio","video"),
|
||||
new AttrD("ontoggle", true, "details"),
|
||||
new AttrD("onunload", true, "body"),
|
||||
new AttrD("onvolumechanged", true, "audio","video"),
|
||||
new AttrD("onwaiting", true, "audio","video"),
|
||||
//new AttrD("onwheel"), //global attribute
|
||||
new AttrD("open", false, "details"),
|
||||
new AttrD("optimum", true, "meter"),
|
||||
new AttrD("pattern", true, "input"),
|
||||
new AttrD("placeholder", true, "input","textarea"),
|
||||
new AttrD("poster", true, "video"),
|
||||
new AttrD("preload", true, "audio","video"),
|
||||
new AttrD("readonly", false, "input","textarea"),
|
||||
new AttrD("rel", true, "a","area","form","link"),
|
||||
new AttrD("required", false, "input","select","textarea"),
|
||||
new AttrD("reversed", false, "ol"),
|
||||
new AttrD("rows", true, "textarea"),
|
||||
new AttrD("rowspan", true, "td","th"),
|
||||
new AttrD("sandbox", false, "iframe"),
|
||||
new AttrD("scope", true, "th"),
|
||||
new AttrD("selected", false, "option"),
|
||||
new AttrD("shape", true, "area"),
|
||||
new AttrD("size", true, "input","select"),
|
||||
new AttrD("sizes", true, "img","link","source"),
|
||||
new AttrD("span", true, "col","colgroup"),
|
||||
//new AttrD("spellcheck"), //global attribute
|
||||
new AttrD("src", true, "audio","embed","iframe","img","input","script","source","track","video"),
|
||||
new AttrD("srcdoc", true, "iframe"),
|
||||
new AttrD("srclang", true, "track"),
|
||||
new AttrD("srcset", true, "img","source"),
|
||||
new AttrD("start", true, "ol"),
|
||||
new AttrD("step", true, "input"),
|
||||
//new AttrD("style"), //global attribute
|
||||
//new AttrD("tabindex"), //global attribute
|
||||
new AttrD("target", true, "a","area","base","form"),
|
||||
//new AttrD("title"), //global attribute
|
||||
//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","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 com.j2html.codegen.wattsi;
|
||||
|
||||
public interface AttributeDefinition {
|
||||
|
||||
String name();
|
||||
|
||||
boolean appliesTo(ElementDefinition element);
|
||||
|
||||
boolean isObsolete();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.j2html.codegen.wattsi;
|
||||
|
||||
public interface ElementDefinition {
|
||||
|
||||
String name();
|
||||
|
||||
boolean isSelfClosing();
|
||||
|
||||
boolean isObsolete();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package com.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 isSelfClosing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
j2html-codegen/src/test/java/com/j2html/codegen/AppTest.java
Normal file
16
j2html-codegen/src/test/java/com/j2html/codegen/AppTest.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.j2html.codegen;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class AppTest
|
||||
{
|
||||
@Test
|
||||
public void shouldAnswerWithTrue()
|
||||
{
|
||||
//dummy, just to conform to the default mvn
|
||||
//directory layout
|
||||
assertTrue( true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.j2html.codegen;
|
||||
|
||||
import com.j2html.codegen.generators.TagCreatorCodeGenerator;
|
||||
import com.j2html.codegen.wattsi.ElementDefinition;
|
||||
import com.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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.j2html.codegen;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class ParserTest {
|
||||
|
||||
private void verifyParsing(String txt, Consumer<Parser.Listener> checks) {
|
||||
Parser.Listener listener = mock(Parser.Listener.class);
|
||||
Parser.parse(txt, listener);
|
||||
checks.accept(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void an_empty_input_has_no_events() {
|
||||
verifyParsing("", listener -> {
|
||||
verifyNoInteractions(listener);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whitespace_has_no_events() {
|
||||
verifyParsing(" \t\t\t\t", listener -> {
|
||||
verifyNoInteractions(listener);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commented_lines_are_signaled() {
|
||||
verifyParsing("#Comment 1.\n# Comment B?", listener -> {
|
||||
InOrder order = inOrder(listener);
|
||||
order.verify(listener).lineCommented(1, "#Comment 1.");
|
||||
order.verify(listener).lineCommented(2, "# Comment B?");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void node_definitions_are_signaled() {
|
||||
verifyParsing("ELEMENT[a]\nEMPTY-ELEMENT[b]\nBOOLEAN[c]\nONOFF[d]\nSTRING[e]", listener -> {
|
||||
InOrder order = inOrder(listener);
|
||||
order.verify(listener).elementDefined(1, "a");
|
||||
order.verify(listener).emptyElementDefined(2, "b");
|
||||
order.verify(listener).booleanDefined(3, "c");
|
||||
order.verify(listener).onOffDefined(4, "d");
|
||||
order.verify(listener).stringDefined(5, "e");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void attribute_definitions_are_signaled() {
|
||||
verifyParsing("ATTRIBUTE[a:b]", listener -> {
|
||||
InOrder order = inOrder(listener);
|
||||
order.verify(listener).attributeDefined(1, "a", "b");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalid_lines_are_signaled() {
|
||||
verifyParsing("lol, I dunno!\nIt Broke...", listener -> {
|
||||
InOrder order = inOrder(listener);
|
||||
order.verify(listener).invalidLine(1, "lol, I dunno!");
|
||||
order.verify(listener).invalidLine(2, "It Broke...");
|
||||
});
|
||||
}
|
||||
}
|
||||
128553
j2html-codegen/src/test/resources/2022-01.wattsi
Normal file
128553
j2html-codegen/src/test/resources/2022-01.wattsi
Normal file
File diff suppressed because it is too large
Load Diff
622
j2html-codegen/src/test/resources/html.model
Normal file
622
j2html-codegen/src/test/resources/html.model
Normal file
@@ -0,0 +1,622 @@
|
||||
EMPTY-ELEMENT[area]
|
||||
EMPTY-ELEMENT[base]
|
||||
EMPTY-ELEMENT[br]
|
||||
EMPTY-ELEMENT[col]
|
||||
EMPTY-ELEMENT[embed]
|
||||
EMPTY-ELEMENT[hr]
|
||||
EMPTY-ELEMENT[img]
|
||||
EMPTY-ELEMENT[input]
|
||||
EMPTY-ELEMENT[keygen]
|
||||
EMPTY-ELEMENT[link]
|
||||
EMPTY-ELEMENT[meta]
|
||||
EMPTY-ELEMENT[param]
|
||||
EMPTY-ELEMENT[source]
|
||||
EMPTY-ELEMENT[track]
|
||||
EMPTY-ELEMENT[wbr]
|
||||
|
||||
ELEMENT[a]
|
||||
ELEMENT[abbr]
|
||||
ELEMENT[address]
|
||||
ELEMENT[article]
|
||||
ELEMENT[aside]
|
||||
ELEMENT[audio]
|
||||
ELEMENT[b]
|
||||
ELEMENT[bdi]
|
||||
ELEMENT[bdo]
|
||||
ELEMENT[blockquote]
|
||||
ELEMENT[body]
|
||||
ELEMENT[button]
|
||||
ELEMENT[canvas]
|
||||
ELEMENT[caption]
|
||||
ELEMENT[cite]
|
||||
ELEMENT[code]
|
||||
ELEMENT[colgroup]
|
||||
ELEMENT[data]
|
||||
ELEMENT[datalist]
|
||||
ELEMENT[dd]
|
||||
ELEMENT[del]
|
||||
ELEMENT[details]
|
||||
ELEMENT[dfn]
|
||||
ELEMENT[dialog]
|
||||
ELEMENT[div]
|
||||
ELEMENT[dl]
|
||||
ELEMENT[dt]
|
||||
ELEMENT[em]
|
||||
ELEMENT[fieldset]
|
||||
ELEMENT[figcaption]
|
||||
ELEMENT[figure]
|
||||
ELEMENT[footer]
|
||||
ELEMENT[form]
|
||||
ELEMENT[h1]
|
||||
ELEMENT[h2]
|
||||
ELEMENT[h3]
|
||||
ELEMENT[h4]
|
||||
ELEMENT[h5]
|
||||
ELEMENT[h6]
|
||||
ELEMENT[head]
|
||||
ELEMENT[header]
|
||||
ELEMENT[html]
|
||||
ELEMENT[i]
|
||||
ELEMENT[iframe]
|
||||
ELEMENT[ins]
|
||||
ELEMENT[kbd]
|
||||
ELEMENT[label]
|
||||
ELEMENT[legend]
|
||||
ELEMENT[li]
|
||||
ELEMENT[main]
|
||||
ELEMENT[map]
|
||||
ELEMENT[mark]
|
||||
ELEMENT[menu]
|
||||
ELEMENT[menuitem]
|
||||
ELEMENT[meter]
|
||||
ELEMENT[nav]
|
||||
ELEMENT[noscript]
|
||||
ELEMENT[object]
|
||||
ELEMENT[ol]
|
||||
ELEMENT[optgroup]
|
||||
ELEMENT[option]
|
||||
ELEMENT[output]
|
||||
ELEMENT[p]
|
||||
ELEMENT[picture]
|
||||
ELEMENT[pre]
|
||||
ELEMENT[progress]
|
||||
ELEMENT[q]
|
||||
ELEMENT[rp]
|
||||
ELEMENT[rt]
|
||||
ELEMENT[ruby]
|
||||
ELEMENT[s]
|
||||
ELEMENT[samp]
|
||||
ELEMENT[script]
|
||||
ELEMENT[section]
|
||||
ELEMENT[select]
|
||||
ELEMENT[slot]
|
||||
ELEMENT[small]
|
||||
ELEMENT[span]
|
||||
ELEMENT[strong]
|
||||
ELEMENT[style]
|
||||
ELEMENT[sub]
|
||||
ELEMENT[summary]
|
||||
ELEMENT[sup]
|
||||
ELEMENT[table]
|
||||
ELEMENT[tbody]
|
||||
ELEMENT[td]
|
||||
ELEMENT[template]
|
||||
ELEMENT[textarea]
|
||||
ELEMENT[tfoot]
|
||||
ELEMENT[th]
|
||||
ELEMENT[thead]
|
||||
ELEMENT[time]
|
||||
ELEMENT[title]
|
||||
ELEMENT[tr]
|
||||
ELEMENT[u]
|
||||
ELEMENT[ul]
|
||||
ELEMENT[var]
|
||||
ELEMENT[video]
|
||||
|
||||
STRING[accept]
|
||||
ATTRIBUTE[input:accept]
|
||||
|
||||
STRING[action]
|
||||
ATTRIBUTE[form:action]
|
||||
|
||||
STRING[alt]
|
||||
ATTRIBUTE[area:alt]
|
||||
ATTRIBUTE[img:alt]
|
||||
ATTRIBUTE[input:alt]
|
||||
|
||||
BOOLEAN[async]
|
||||
ATTRIBUTE[script:async]
|
||||
|
||||
ONOFF[autocomplete]
|
||||
ATTRIBUTE[form:autocomplete]
|
||||
ATTRIBUTE[input:autocomplete]
|
||||
|
||||
BOOLEAN[autofocus]
|
||||
ATTRIBUTE[button:autofocus]
|
||||
ATTRIBUTE[input:autofocus]
|
||||
ATTRIBUTE[select:autofocus]
|
||||
ATTRIBUTE[textarea:autofocus]
|
||||
|
||||
BOOLEAN[autoplay]
|
||||
ATTRIBUTE[audio:autoplay]
|
||||
ATTRIBUTE[video:autoplay]
|
||||
|
||||
STRING[charset]
|
||||
ATTRIBUTE[meta:charset]
|
||||
ATTRIBUTE[script:charset]
|
||||
|
||||
BOOLEAN[checked]
|
||||
ATTRIBUTE[input:checked]
|
||||
|
||||
STRING[cite]
|
||||
ATTRIBUTE[blockquote:cite]
|
||||
ATTRIBUTE[del:cite]
|
||||
ATTRIBUTE[ins:cite]
|
||||
ATTRIBUTE[q:cite]
|
||||
|
||||
STRING[cols]
|
||||
ATTRIBUTE[textarea:cols]
|
||||
|
||||
STRING[colspan]
|
||||
ATTRIBUTE[td:colspan]
|
||||
ATTRIBUTE[th:colspan]
|
||||
|
||||
STRING[content]
|
||||
ATTRIBUTE[meta:content]
|
||||
|
||||
BOOLEAN[controls]
|
||||
ATTRIBUTE[audio:controls]
|
||||
ATTRIBUTE[video:controls]
|
||||
|
||||
STRING[coords]
|
||||
ATTRIBUTE[area:coords]
|
||||
|
||||
STRING[data]
|
||||
ATTRIBUTE[object:data]
|
||||
|
||||
STRING[datetime]
|
||||
ATTRIBUTE[del:datetime]
|
||||
ATTRIBUTE[ins:datetime]
|
||||
ATTRIBUTE[time:datetime]
|
||||
|
||||
BOOLEAN[default]
|
||||
ATTRIBUTE[track:default]
|
||||
|
||||
BOOLEAN[defer]
|
||||
ATTRIBUTE[script:defer]
|
||||
|
||||
STRING[dirname]
|
||||
ATTRIBUTE[input:dirname]
|
||||
ATTRIBUTE[textarea:dirname]
|
||||
|
||||
BOOLEAN[disabled]
|
||||
ATTRIBUTE[button:disabled]
|
||||
ATTRIBUTE[fieldset:disabled]
|
||||
ATTRIBUTE[input:disabled]
|
||||
ATTRIBUTE[optgroup:disabled]
|
||||
ATTRIBUTE[option:disabled]
|
||||
ATTRIBUTE[select:disabled]
|
||||
ATTRIBUTE[textarea:disabled]
|
||||
|
||||
BOOLEAN[download]
|
||||
ATTRIBUTE[a:download]
|
||||
ATTRIBUTE[area:download]
|
||||
|
||||
STRING[enctype]
|
||||
ATTRIBUTE[form:enctype]
|
||||
|
||||
STRING[for]
|
||||
ATTRIBUTE[label:for]
|
||||
ATTRIBUTE[output:for]
|
||||
|
||||
STRING[form]
|
||||
ATTRIBUTE[button:form]
|
||||
ATTRIBUTE[fieldset:form]
|
||||
ATTRIBUTE[input:form]
|
||||
ATTRIBUTE[label:form]
|
||||
ATTRIBUTE[meter:form]
|
||||
ATTRIBUTE[object:form]
|
||||
ATTRIBUTE[output:form]
|
||||
ATTRIBUTE[select:form]
|
||||
ATTRIBUTE[textarea:form]
|
||||
|
||||
STRING[formaction]
|
||||
ATTRIBUTE[button:formaction]
|
||||
ATTRIBUTE[input:formaction]
|
||||
|
||||
STRING[headers]
|
||||
ATTRIBUTE[td:headers]
|
||||
ATTRIBUTE[th:headers]
|
||||
|
||||
STRING[height]
|
||||
ATTRIBUTE[canvas:height]
|
||||
ATTRIBUTE[embed:height]
|
||||
ATTRIBUTE[iframe:height]
|
||||
ATTRIBUTE[img:height]
|
||||
ATTRIBUTE[input:height]
|
||||
ATTRIBUTE[object:height]
|
||||
ATTRIBUTE[video:height]
|
||||
|
||||
STRING[high]
|
||||
ATTRIBUTE[meter:high]
|
||||
|
||||
STRING[href]
|
||||
ATTRIBUTE[a:href]
|
||||
ATTRIBUTE[area:href]
|
||||
ATTRIBUTE[base:href]
|
||||
ATTRIBUTE[link:href]
|
||||
|
||||
STRING[hreflang]
|
||||
ATTRIBUTE[a:hreflang]
|
||||
ATTRIBUTE[area:hreflang]
|
||||
ATTRIBUTE[link:hreflang]
|
||||
|
||||
BOOLEAN[ismap]
|
||||
ATTRIBUTE[img:ismap]
|
||||
|
||||
STRING[kind]
|
||||
ATTRIBUTE[track:kind]
|
||||
|
||||
STRING[label]
|
||||
ATTRIBUTE[track:label]
|
||||
ATTRIBUTE[option:label]
|
||||
ATTRIBUTE[optgroup:label]
|
||||
|
||||
STRING[list]
|
||||
ATTRIBUTE[input:list]
|
||||
|
||||
BOOLEAN[loop]
|
||||
ATTRIBUTE[audio:loop]
|
||||
ATTRIBUTE[video:loop]
|
||||
|
||||
STRING[low]
|
||||
ATTRIBUTE[meter:low]
|
||||
|
||||
STRING[max]
|
||||
ATTRIBUTE[input:max]
|
||||
ATTRIBUTE[meter:max]
|
||||
ATTRIBUTE[progress:max]
|
||||
|
||||
STRING[maxlength]
|
||||
ATTRIBUTE[input:maxlength]
|
||||
ATTRIBUTE[textarea:maxlength]
|
||||
|
||||
STRING[media]
|
||||
ATTRIBUTE[a:media]
|
||||
ATTRIBUTE[area:media]
|
||||
ATTRIBUTE[link:media]
|
||||
ATTRIBUTE[source:media]
|
||||
ATTRIBUTE[style:media]
|
||||
|
||||
STRING[method]
|
||||
ATTRIBUTE[form:method]
|
||||
|
||||
STRING[min]
|
||||
ATTRIBUTE[input:min]
|
||||
ATTRIBUTE[meter:min]
|
||||
|
||||
BOOLEAN[multiple]
|
||||
ATTRIBUTE[input:multiple]
|
||||
ATTRIBUTE[select:multiple]
|
||||
|
||||
BOOLEAN[muted]
|
||||
ATTRIBUTE[video:muted]
|
||||
ATTRIBUTE[audio:muted]
|
||||
|
||||
STRING[name]
|
||||
ATTRIBUTE[button:name]
|
||||
ATTRIBUTE[fieldset:name]
|
||||
ATTRIBUTE[form:name]
|
||||
ATTRIBUTE[iframe:name]
|
||||
ATTRIBUTE[input:name]
|
||||
ATTRIBUTE[map:name]
|
||||
ATTRIBUTE[meta:name]
|
||||
ATTRIBUTE[object:name]
|
||||
ATTRIBUTE[output:name]
|
||||
ATTRIBUTE[param:name]
|
||||
ATTRIBUTE[select:name]
|
||||
ATTRIBUTE[slot:name]
|
||||
ATTRIBUTE[textarea:name]
|
||||
|
||||
BOOLEAN[novalidate]
|
||||
ATTRIBUTE[form:novalidate]
|
||||
|
||||
STRING[onabort]
|
||||
ATTRIBUTE[audio:onabort]
|
||||
ATTRIBUTE[embed:onabort]
|
||||
ATTRIBUTE[img:onabort]
|
||||
ATTRIBUTE[object:onabort]
|
||||
ATTRIBUTE[video:onabort]
|
||||
|
||||
STRING[onafterprint]
|
||||
ATTRIBUTE[body:onafterprint]
|
||||
|
||||
STRING[onbeforeprint]
|
||||
ATTRIBUTE[body:onbeforeprint]
|
||||
|
||||
STRING[onbeforeunload]
|
||||
ATTRIBUTE[body:onbeforeunload]
|
||||
|
||||
STRING[oncanplay]
|
||||
ATTRIBUTE[audio:oncanplay]
|
||||
ATTRIBUTE[embed:oncanplay]
|
||||
ATTRIBUTE[object:oncanplay]
|
||||
ATTRIBUTE[video:oncanplay]
|
||||
|
||||
STRING[oncanplaythrough]
|
||||
ATTRIBUTE[audio:oncanplaythrough]
|
||||
ATTRIBUTE[video:oncanplaythrough]
|
||||
|
||||
STRING[oncuechange]
|
||||
ATTRIBUTE[track:oncuechange]
|
||||
|
||||
STRING[ondurationchange]
|
||||
ATTRIBUTE[audio:ondurationchange]
|
||||
ATTRIBUTE[video:ondurationchange]
|
||||
|
||||
STRING[onemptied]
|
||||
ATTRIBUTE[audio:onemptied]
|
||||
ATTRIBUTE[video:onemptied]
|
||||
|
||||
STRING[onended]
|
||||
ATTRIBUTE[audio:onended]
|
||||
ATTRIBUTE[video:onended]
|
||||
|
||||
STRING[onerror]
|
||||
ATTRIBUTE[audio:onerror]
|
||||
ATTRIBUTE[body:onerror]
|
||||
ATTRIBUTE[embed:onerror]
|
||||
ATTRIBUTE[img:onerror]
|
||||
ATTRIBUTE[object:onerror]
|
||||
ATTRIBUTE[script:onerror]
|
||||
ATTRIBUTE[style:onerror]
|
||||
ATTRIBUTE[video:onerror]
|
||||
|
||||
STRING[onhashchange]
|
||||
ATTRIBUTE[body:onhashchange]
|
||||
|
||||
STRING[onload]
|
||||
ATTRIBUTE[body:onload]
|
||||
ATTRIBUTE[iframe:onload]
|
||||
ATTRIBUTE[img:onload]
|
||||
ATTRIBUTE[input:onload]
|
||||
ATTRIBUTE[link:onload]
|
||||
ATTRIBUTE[script:onload]
|
||||
ATTRIBUTE[style:onload]
|
||||
|
||||
STRING[onloadeddata]
|
||||
ATTRIBUTE[audio:onloadeddata]
|
||||
ATTRIBUTE[video:onloadeddata]
|
||||
|
||||
STRING[onloadedmetadata]
|
||||
ATTRIBUTE[audio:onloadedmetadata]
|
||||
ATTRIBUTE[video:onloadedmetadata]
|
||||
|
||||
STRING[onloadstart]
|
||||
ATTRIBUTE[audio:onloadstart]
|
||||
ATTRIBUTE[video:onloadstart]
|
||||
|
||||
STRING[onoffline]
|
||||
ATTRIBUTE[body:onoffline]
|
||||
|
||||
STRING[ononline]
|
||||
ATTRIBUTE[body:ononline]
|
||||
|
||||
STRING[onpagehide]
|
||||
ATTRIBUTE[body:onpagehide]
|
||||
|
||||
STRING[onpageshow]
|
||||
ATTRIBUTE[body:onpageshow]
|
||||
|
||||
STRING[onpause]
|
||||
ATTRIBUTE[audio:onpause]
|
||||
ATTRIBUTE[video:onpause]
|
||||
|
||||
STRING[onplay]
|
||||
ATTRIBUTE[audio:onplay]
|
||||
ATTRIBUTE[video:onplay]
|
||||
|
||||
STRING[onplaying]
|
||||
ATTRIBUTE[audio:onplaying]
|
||||
ATTRIBUTE[video:onplaying]
|
||||
|
||||
STRING[onpopstate]
|
||||
ATTRIBUTE[body:onpopstate]
|
||||
|
||||
STRING[onprogress]
|
||||
ATTRIBUTE[audio:onprogress]
|
||||
ATTRIBUTE[video:onprogress]
|
||||
|
||||
STRING[onratechange]
|
||||
ATTRIBUTE[audio:onratechange]
|
||||
ATTRIBUTE[video:onratechange]
|
||||
|
||||
STRING[onreset]
|
||||
ATTRIBUTE[form:onreset]
|
||||
|
||||
STRING[onresize]
|
||||
ATTRIBUTE[body:onresize]
|
||||
|
||||
STRING[onsearch]
|
||||
ATTRIBUTE[input:onsearch]
|
||||
|
||||
STRING[onseeked]
|
||||
ATTRIBUTE[audio:onseeked]
|
||||
ATTRIBUTE[video:onseeked]
|
||||
|
||||
STRING[onseeking]
|
||||
ATTRIBUTE[audio:onseeking]
|
||||
ATTRIBUTE[video:onseeking]
|
||||
|
||||
STRING[onstalled]
|
||||
ATTRIBUTE[audio:onstalled]
|
||||
ATTRIBUTE[video:onstalled]
|
||||
|
||||
STRING[onstorage]
|
||||
ATTRIBUTE[body:onstorage]
|
||||
|
||||
STRING[onsubmit]
|
||||
ATTRIBUTE[form:onsubmit]
|
||||
|
||||
STRING[onsuspend]
|
||||
ATTRIBUTE[audio:onsuspend]
|
||||
ATTRIBUTE[video:onsuspend]
|
||||
|
||||
STRING[ontimeupdate]
|
||||
ATTRIBUTE[audio:ontimeupdate]
|
||||
ATTRIBUTE[video:ontimeupdate]
|
||||
|
||||
STRING[ontoggle]
|
||||
ATTRIBUTE[details:ontoggle]
|
||||
|
||||
STRING[onunload]
|
||||
ATTRIBUTE[body:onunload]
|
||||
|
||||
STRING[onvolumechanged]
|
||||
ATTRIBUTE[audio:onvolumechanged]
|
||||
ATTRIBUTE[video:onvolumechanged]
|
||||
|
||||
STRING[onwaiting]
|
||||
ATTRIBUTE[audio:onwaiting]
|
||||
ATTRIBUTE[video:onwaiting]
|
||||
|
||||
BOOLEAN[open]
|
||||
ATTRIBUTE[details:open]
|
||||
|
||||
STRING[optimum]
|
||||
ATTRIBUTE[meter:optimum]
|
||||
|
||||
STRING[pattern]
|
||||
ATTRIBUTE[input:pattern]
|
||||
|
||||
STRING[placeholder]
|
||||
ATTRIBUTE[input:placeholder]
|
||||
ATTRIBUTE[textarea:placeholder]
|
||||
|
||||
STRING[poster]
|
||||
ATTRIBUTE[video:poster]
|
||||
|
||||
STRING[preload]
|
||||
ATTRIBUTE[audio:preload]
|
||||
ATTRIBUTE[video:preload]
|
||||
|
||||
BOOLEAN[readonly]
|
||||
ATTRIBUTE[input:readonly]
|
||||
ATTRIBUTE[textarea:readonly]
|
||||
|
||||
STRING[rel]
|
||||
ATTRIBUTE[a:rel]
|
||||
ATTRIBUTE[area:rel]
|
||||
ATTRIBUTE[form:rel]
|
||||
ATTRIBUTE[link:rel]
|
||||
|
||||
BOOLEAN[required]
|
||||
ATTRIBUTE[input:required]
|
||||
ATTRIBUTE[select:required]
|
||||
ATTRIBUTE[textarea:required]
|
||||
|
||||
BOOLEAN[reversed]
|
||||
ATTRIBUTE[ol:reversed]
|
||||
|
||||
STRING[rows]
|
||||
ATTRIBUTE[textarea:rows]
|
||||
|
||||
STRING[rowspan]
|
||||
ATTRIBUTE[td:rowspan]
|
||||
ATTRIBUTE[th:rowspan]
|
||||
|
||||
BOOLEAN[sandbox]
|
||||
ATTRIBUTE[iframe:sandbox]
|
||||
|
||||
STRING[scope]
|
||||
ATTRIBUTE[th:scope]
|
||||
|
||||
BOOLEAN[selected]
|
||||
ATTRIBUTE[option:selected]
|
||||
|
||||
STRING[shape]
|
||||
ATTRIBUTE[area:shape]
|
||||
|
||||
STRING[size]
|
||||
ATTRIBUTE[input:size]
|
||||
ATTRIBUTE[select:size]
|
||||
|
||||
STRING[sizes]
|
||||
ATTRIBUTE[img:sizes]
|
||||
ATTRIBUTE[link:sizes]
|
||||
ATTRIBUTE[source:sizes]
|
||||
|
||||
STRING[span]
|
||||
ATTRIBUTE[col:span]
|
||||
ATTRIBUTE[colgroup:span]
|
||||
|
||||
STRING[src]
|
||||
ATTRIBUTE[audio:src]
|
||||
ATTRIBUTE[embed:src]
|
||||
ATTRIBUTE[iframe:src]
|
||||
ATTRIBUTE[img:src]
|
||||
ATTRIBUTE[input:src]
|
||||
ATTRIBUTE[script:src]
|
||||
ATTRIBUTE[source:src]
|
||||
ATTRIBUTE[track:src]
|
||||
ATTRIBUTE[video:src]
|
||||
|
||||
STRING[srcdoc]
|
||||
ATTRIBUTE[iframe:srcdoc]
|
||||
|
||||
STRING[srclang]
|
||||
ATTRIBUTE[track:srclang]
|
||||
|
||||
STRING[srcset]
|
||||
ATTRIBUTE[img:srcset]
|
||||
ATTRIBUTE[source:srcset]
|
||||
|
||||
STRING[start]
|
||||
ATTRIBUTE[ol:start]
|
||||
|
||||
STRING[step]
|
||||
ATTRIBUTE[input:step]
|
||||
|
||||
STRING[target]
|
||||
ATTRIBUTE[a:target]
|
||||
ATTRIBUTE[area:target]
|
||||
ATTRIBUTE[base:target]
|
||||
ATTRIBUTE[form:target]
|
||||
|
||||
STRING[type]
|
||||
ATTRIBUTE[a:type]
|
||||
ATTRIBUTE[button:type]
|
||||
ATTRIBUTE[embed:type]
|
||||
ATTRIBUTE[input:type]
|
||||
ATTRIBUTE[link:type]
|
||||
ATTRIBUTE[menu:type]
|
||||
ATTRIBUTE[object:type]
|
||||
ATTRIBUTE[script:type]
|
||||
ATTRIBUTE[source:type]
|
||||
ATTRIBUTE[style:type]
|
||||
|
||||
STRING[usemap]
|
||||
ATTRIBUTE[img:usemap]
|
||||
ATTRIBUTE[object:usemap]
|
||||
|
||||
STRING[value]
|
||||
ATTRIBUTE[button:value]
|
||||
ATTRIBUTE[data:value]
|
||||
ATTRIBUTE[input:value]
|
||||
ATTRIBUTE[li:value]
|
||||
ATTRIBUTE[option:value]
|
||||
ATTRIBUTE[meter:value]
|
||||
ATTRIBUTE[progress:value]
|
||||
ATTRIBUTE[param:value]
|
||||
|
||||
STRING[width]
|
||||
ATTRIBUTE[canvas:width]
|
||||
ATTRIBUTE[embed:width]
|
||||
ATTRIBUTE[iframe:width]
|
||||
ATTRIBUTE[img:width]
|
||||
ATTRIBUTE[input:width]
|
||||
ATTRIBUTE[object:width]
|
||||
ATTRIBUTE[video:width]
|
||||
|
||||
STRING[wrap]
|
||||
ATTRIBUTE[textarea:wrap]
|
||||
Reference in New Issue
Block a user