initial commit

This commit is contained in:
Jörg Prante 2024-03-02 22:53:34 +01:00
commit f5d00d8913
342 changed files with 140728 additions and 0 deletions

16
.gitignore vendored Normal file
View file

@ -0,0 +1,16 @@
/.settings
/.classpath
/.project
/.gradle
**/data
**/work
**/logs
**/.idea
**/target
**/out
**/build
.DS_Store
*.iml
*~
*.key
*.crt

202
LICENSE.txt Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

35
build.gradle Normal file
View file

@ -0,0 +1,35 @@
plugins {
id 'maven-publish'
id 'signing'
id "io.github.gradle-nexus.publish-plugin" version "2.0.0-rc-1"
}
wrapper {
gradleVersion = libs.versions.gradle.get()
distributionType = Wrapper.DistributionType.BIN
}
ext {
user = 'joerg'
name = 'j2html'
description = 'Java to HTML generator'
inceptionYear = '2024'
url = 'https://xbib.org/' + user + '/' + name
scmUrl = 'https://xbib.org/' + user + '/' + name
scmConnection = 'scm:git:git://xbib.org/' + user + '/' + name + '.git'
scmDeveloperConnection = 'scm:git:ssh://forgejo@xbib.org:' + user + '/' + name + '.git'
issueManagementSystem = 'Github'
issueManagementUrl = ext.scmUrl + '/issues'
licenseName = 'The Apache License, Version 2.0'
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
subprojects {
apply from: rootProject.file('gradle/repositories/maven.gradle')
apply from: rootProject.file('gradle/compile/java.gradle')
apply from: rootProject.file('gradle/test/junit5.gradle')
apply from: rootProject.file('gradle/publish/maven.gradle')
apply from: rootProject.file('gradle/publish/forgejo.gradle')
}
apply from: rootProject.file('gradle/publish/sonatype.gradle')

3
gradle.properties Normal file
View file

@ -0,0 +1,3 @@
group = org.xbib
name = j2html
version = 1.6.0

View file

@ -0,0 +1,29 @@
apply plugin: 'java-library'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
modularity.inferModulePath.set(true)
withSourcesJar()
withJavadocJar()
}
jar {
manifest {
attributes('Implementation-Version': project.version)
}
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
tasks.withType(JavaCompile) {
options.fork = true
options.forkOptions.jvmArgs += ['-Duser.language=en','-Duser.country=US']
options.compilerArgs.add('-Xlint:all')
options.encoding = 'UTF-8'
}
tasks.withType(Javadoc) {
options.addStringOption('Xdoclint:none', '-quiet')
options.encoding = 'UTF-8'
}

View file

@ -0,0 +1,13 @@
apply plugin: 'org.xbib.gradle.plugin.asciidoctor'
asciidoctor {
attributes 'source-highlighter': 'coderay',
toc: 'left',
doctype: 'book',
icons: 'font',
encoding: 'utf-8',
sectlink: true,
sectanchors: true,
linkattrs: true,
imagesdir: 'img'
}

8
gradle/ide/idea.gradle Normal file
View file

@ -0,0 +1,8 @@
apply plugin: 'idea'
idea {
module {
outputDir file('build/classes/java/main')
testOutputDir file('build/classes/java/test')
}
}

View file

@ -0,0 +1,16 @@
if (project.hasProperty('forgeJoToken')) {
publishing {
repositories {
maven {
url 'https://xbib.org/api/packages/joerg/maven'
credentials(HttpHeaderCredentials) {
name = "Authorization"
value = "token ${project.property('forgeJoToken')}"
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
}
}

27
gradle/publish/ivy.gradle Normal file
View file

@ -0,0 +1,27 @@
apply plugin: 'ivy-publish'
publishing {
repositories {
ivy {
url = "https://xbib.org/repo"
}
}
publications {
ivy(IvyPublication) {
from components.java
descriptor {
license {
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
author {
name = 'Jörg Prante'
url = 'http://example.com/users/jane'
}
descriptor.description {
text = rootProject.ext.description
}
}
}
}
}

View file

@ -0,0 +1,51 @@
publishing {
publications {
"${project.name}"(MavenPublication) {
from components.java
pom {
artifactId = project.name
name = project.name
description = rootProject.ext.description
url = rootProject.ext.url
inceptionYear = rootProject.ext.inceptionYear
packaging = 'jar'
organization {
name = 'xbib'
url = 'https://xbib.org'
}
developers {
developer {
id = 'jprante'
name = 'Jörg Prante'
email = 'joergprante@gmail.com'
url = 'https://xbib.org/joerg'
}
}
scm {
url = rootProject.ext.scmUrl
connection = rootProject.ext.scmConnection
developerConnection = rootProject.ext.scmDeveloperConnection
}
issueManagement {
system = rootProject.ext.issueManagementSystem
url = rootProject.ext.issueManagementUrl
}
licenses {
license {
name = rootProject.ext.licenseName
url = rootProject.ext.licenseUrl
distribution = 'repo'
}
}
}
}
}
}
if (project.hasProperty("signing.keyId")) {
apply plugin: 'signing'
signing {
sign publishing.publications."${project.name}"
}
}

View file

@ -0,0 +1,12 @@
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
nexusPublishing {
repositories {
sonatype {
username = project.property('ossrhUsername')
password = project.property('ossrhPassword')
packageGroup = "org.xbib"
}
}
}
}

View file

@ -0,0 +1,19 @@
apply plugin: 'checkstyle'
tasks.withType(Checkstyle) {
ignoreFailures = true
reports {
xml.getRequired().set(true)
html.getRequired().set(true)
}
}
checkstyle {
configFile = rootProject.file('gradle/quality/checkstyle.xml')
ignoreFailures = true
showViolations = true
checkstyleMain {
source = sourceSets.main.allSource
}
}

View file

@ -0,0 +1,333 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!-- This is a checkstyle configuration file. For descriptions of
what the following rules do, please see the checkstyle configuration
page at http://checkstyle.sourceforge.net/config.html -->
<module name="Checker">
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value=".*(Example|Test|module-info)(\$.*)?"/>
</module>
<module name="FileTabCharacter">
<!-- Checks that there are no tab characters in the file.
-->
</module>
<module name="NewlineAtEndOfFile">
<property name="lineSeparator" value="lf"/>
</module>
<module name="RegexpSingleline">
<!-- Checks that FIXME is not used in comments. TODO is preferred.
-->
<property name="format" value="((//.*)|(\*.*))FIXME" />
<property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="RegexpSingleline">
<!-- Checks that TODOs are named. (Actually, just that they are followed
by an open paren.)
-->
<property name="format" value="((//.*)|(\*.*))TODO[^(]" />
<property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="JavadocPackage">
<!-- Checks that each Java package has a Javadoc file used for commenting.
Only allows a package-info.java, not package.html. -->
</module>
<!-- All Java AST specific tests live under TreeWalker module. -->
<module name="TreeWalker">
<!--
IMPORT CHECKS
-->
<module name="RedundantImport">
<!-- Checks for redundant import statements. -->
<property name="severity" value="error"/>
</module>
<module name="ImportOrder">
<!-- Checks for out of order import statements. -->
<property name="severity" value="warning"/>
<!-- <property name="tokens" value="IMPORT, STATIC_IMPORT"/> -->
<property name="separated" value="false"/>
<property name="groups" value="*"/>
<!-- <property name="option" value="above"/> -->
<property name="sortStaticImportsAlphabetically" value="true"/>
</module>
<module name="CustomImportOrder">
<!-- <property name="customImportOrderRules" value="THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###STATIC"/> -->
<!-- <property name="specialImportsRegExp" value="^javax\."/> -->
<!-- <property name="standardPackageRegExp" value="^java\."/> -->
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="false"/>
</module>
<!--
JAVADOC CHECKS
-->
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
<module name="JavadocMethod">
<property name="accessModifiers" value="protected"/>
<property name="severity" value="warning"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
</module>
<module name="JavadocType">
<property name="scope" value="protected"/>
<property name="severity" value="error"/>
</module>
<module name="JavadocStyle">
<property name="severity" value="warning"/>
</module>
<!--
NAMING CHECKS
-->
<!-- Item 38 - Adhere to generally accepted naming conventions -->
<module name="PackageName">
<!-- Validates identifiers for package names against the
supplied expression. -->
<!-- Here the default checkstyle rule restricts package name parts to
seven characters, this is not in line with common practice at Google.
-->
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
<property name="severity" value="warning"/>
</module>
<module name="TypeNameCheck">
<!-- Validates static, final fields against the
expression "^[A-Z][a-zA-Z0-9]*$". -->
<metadata name="altname" value="TypeName"/>
<property name="severity" value="warning"/>
</module>
<module name="ConstantNameCheck">
<!-- Validates non-private, static, final fields against the supplied
public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
<metadata name="altname" value="ConstantName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="false"/>
<property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$"/>
<message key="name.invalidPattern"
value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)."/>
<property name="severity" value="warning"/>
</module>
<module name="StaticVariableNameCheck">
<!-- Validates static, non-final fields against the supplied
expression "^[a-z][a-zA-Z0-9]*_?$". -->
<metadata name="altname" value="StaticVariableName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
<property name="severity" value="warning"/>
</module>
<module name="MemberNameCheck">
<!-- Validates non-static members against the supplied expression. -->
<metadata name="altname" value="MemberName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<property name="severity" value="warning"/>
</module>
<module name="MethodNameCheck">
<!-- Validates identifiers for method names. -->
<metadata name="altname" value="MethodName"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
<property name="severity" value="warning"/>
</module>
<module name="ParameterName">
<!-- Validates identifiers for method parameters against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalFinalVariableName">
<!-- Validates identifiers for local final variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalVariableName">
<!-- Validates identifiers for local variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<!--
LENGTH and CODING CHECKS
-->
<module name="LeftCurly">
<!-- Checks for placement of the left curly brace ('{'). -->
<property name="severity" value="warning"/>
</module>
<module name="RightCurly">
<!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
the same line. e.g., the following example is fine:
<pre>
if {
...
} else
</pre>
-->
<!-- This next example is not fine:
<pre>
if {
...
}
else
</pre>
-->
<property name="option" value="same"/>
<property name="severity" value="warning"/>
</module>
<!-- Checks for braces around if and else blocks -->
<module name="NeedBraces">
<property name="severity" value="warning"/>
<property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
</module>
<module name="UpperEll">
<!-- Checks that long constants are defined with an upper ell.-->
<property name="severity" value="error"/>
</module>
<module name="FallThrough">
<!-- Warn about falling through to the next case statement. Similar to
javac -Xlint:fallthrough, but the check is suppressed if a single-line comment
on the last non-blank line preceding the fallen-into case contains 'fall through' (or
some other variants which we don't publicized to promote consistency).
-->
<property name="reliefPattern"
value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/>
<property name="severity" value="error"/>
</module>
<!--
MODIFIERS CHECKS
-->
<module name="ModifierOrder">
<!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
8.4.3. The prescribed order is:
public, protected, private, abstract, static, final, transient, volatile,
synchronized, native, strictfp
-->
</module>
<!--
WHITESPACE CHECKS
-->
<module name="WhitespaceAround">
<!-- Checks that various tokens are surrounded by whitespace.
This includes most binary operators and keywords followed
by regular or curly braces.
-->
<property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
<property name="severity" value="error"/>
</module>
<module name="WhitespaceAfter">
<!-- Checks that commas, semicolons and typecasts are followed by
whitespace.
-->
<property name="tokens" value="COMMA, SEMI, TYPECAST"/>
</module>
<module name="NoWhitespaceAfter">
<!-- Checks that there is no whitespace after various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
UNARY_PLUS"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="NoWhitespaceBefore">
<!-- Checks that there is no whitespace before various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="ParenPad">
<!-- Checks that there is no whitespace before close parens or after
open parens.
-->
<property name="severity" value="warning"/>
</module>
</module>
<module name="LineLength">
<!-- Checks if a line is too long. -->
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="128"/>
<property name="severity" value="error"/>
<!--
The default ignore pattern exempts the following elements:
- import statements
- long URLs inside comments
-->
<property name="ignorePattern"
value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
default="^(package .*;\s*)|(import .*;\s*)|( *(\*|//).*https?://.*)$"/>
</module>
</module>

View file

@ -0,0 +1,11 @@
cyclonedxBom {
includeConfigs = [ 'runtimeClasspath' ]
skipConfigs = [ 'compileClasspath', 'testCompileClasspath' ]
projectType = "library"
schemaVersion = "1.4"
destination = file("build/reports")
outputName = "bom"
outputFormat = "json"
includeBomSerialNumber = true
componentVersion = "2.0.0"
}

17
gradle/quality/pmd.gradle Normal file
View file

@ -0,0 +1,17 @@
apply plugin: 'pmd'
tasks.withType(Pmd) {
ignoreFailures = true
reports {
xml.getRequired().set(true)
html.getRequired().set(true)
}
}
pmd {
ignoreFailures = true
consoleOutput = false
toolVersion = "6.51.0"
ruleSetFiles = rootProject.files('gradle/quality/pmd/category/java/bestpractices.xml')
}

View file

@ -0,0 +1,10 @@
sonarqube {
properties {
property "sonar.projectName", "${project.group} ${project.name}"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.tests", "src/test/java"
property "sonar.scm.provider", "git"
property "sonar.junit.reportsPath", "build/test-results/test/"
}
}

View file

@ -0,0 +1,15 @@
apply plugin: 'com.github.spotbugs'
spotbugs {
effort = "max"
reportLevel = "low"
ignoreFailures = true
}
spotbugsMain {
reports {
xml.getRequired().set(false)
html.getRequired().set(true)
}
}

View file

@ -0,0 +1,4 @@
repositories {
mavenLocal()
mavenCentral()
}

22
gradle/test/jmh.gradle Normal file
View file

@ -0,0 +1,22 @@
sourceSets {
jmh {
java.srcDirs = ['src/jmh/java']
resources.srcDirs = ['src/jmh/resources']
compileClasspath += sourceSets.main.runtimeClasspath
}
}
dependencies {
jmhImplementation 'org.openjdk.jmh:jmh-core:1.34'
jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.34'
}
task jmh(type: JavaExec, group: 'jmh', dependsOn: jmhClasses) {
mainClass.set('org.openjdk.jmh.Main')
classpath = sourceSets.jmh.compileClasspath + sourceSets.jmh.runtimeClasspath
project.file('build/reports/jmh').mkdirs()
args '-rf', 'json'
args '-rff', project.file('build/reports/jmh/result.json')
}
classes.finalizedBy(jmhClasses)

35
gradle/test/junit5.gradle Normal file
View file

@ -0,0 +1,35 @@
dependencies {
testImplementation testLibs.junit.jupiter.api
testImplementation testLibs.hamcrest
testRuntimeOnly testLibs.junit.jupiter.engine
testRuntimeOnly testLibs.junit.jupiter.platform.launcher
}
test {
useJUnitPlatform()
failFast = false
jvmArgs '--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED',
'--add-exports=java.base/jdk.internal.ref=ALL-UNNAMED',
'--add-exports=java.base/sun.nio.ch=ALL-UNNAMED',
'--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
'--add-opens=jdk.compiler/com.sun.tools.javac=ALL-UNNAMED',
'--add-opens=java.base/java.lang=ALL-UNNAMED',
'--add-opens=java.base/java.lang.reflect=ALL-UNNAMED',
'--add-opens=java.base/java.io=ALL-UNNAMED',
'--add-opens=java.base/java.nio=ALL-UNNAMED',
'--add-opens=java.base/java.util=ALL-UNNAMED'
systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties'
testLogging {
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'
}
afterSuite { desc, result ->
if (!desc.parent) {
println "\nTest result: ${result.resultType}"
println "Test summary: ${result.testCount} tests, " +
"${result.successfulTestCount} succeeded, " +
"${result.failedTestCount} failed, " +
"${result.skippedTestCount} skipped"
}
}
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

249
gradlew vendored Executable file
View file

@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

92
gradlew.bat vendored Normal file
View file

@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -0,0 +1,5 @@
dependencies {
implementation libs.jsoup
implementation libs.javapoet
testImplementation testLibs.mockito.core
}

View file

@ -0,0 +1,172 @@
package org.xbib.j2html.codegen;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import javax.lang.model.element.Modifier;
import org.xbib.j2html.codegen.Model.Node;
import static org.xbib.j2html.codegen.Model.Metadata.ON_OFF;
import static org.xbib.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<>();
for (Node element : model.elements()) {
ClassName className = ClassName.get(pkg, capitalize(element.name) + "Tag");
TypeSpec.Builder type = defineElementClass(element, className);
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<>();
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();
}
}

View file

@ -0,0 +1,179 @@
package org.xbib.j2html.codegen;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.xbib.j2html.codegen.Model.Metadata.ON_OFF;
import static org.xbib.j2html.codegen.Model.Metadata.SELF_CLOSING;
import static org.xbib.j2html.codegen.Model.Node.Type.BOOLEAN;
import static org.xbib.j2html.codegen.Model.Node.Type.ELEMENT;
import static org.xbib.j2html.codegen.Model.Node.Type.STRING;
public class Model implements Parser.Listener {
private final Map<String, Node> elements;
private final 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
}
@SuppressWarnings("serial")
public static class InvalidModel extends RuntimeException {
public InvalidModel(Exception cause, int line) {
super(cause.getMessage() + ". At line " + line, cause);
}
}
@SuppressWarnings("serial")
public static class NodeAlreadyExists extends RuntimeException {
public NodeAlreadyExists(String name) {
super("Node already exists: " + name);
}
}
@SuppressWarnings("serial")
public static class NodeDoesNotExist extends RuntimeException {
public NodeDoesNotExist(String name) {
super("Node does not exist: " + name);
}
}
}

View file

@ -0,0 +1,91 @@
package org.xbib.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;
}
}

View file

@ -0,0 +1,127 @@
package org.xbib.j2html.codegen.generators;
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 org.xbib.j2html.codegen.model.AttrD;
import org.xbib.j2html.codegen.model.AttributesList;
public final class AttributeInterfaceCodeGenerator {
private static final String relPath = "tags/attributes/";
public static void generate(final Path absPath,
final boolean delete) throws IOException {
for (final AttrD attr : AttributesList.attributesDescriptive()) {
final Path path = makePath(attr.attr, absPath);
final String interfaceName = interfaceNameFromAttribute(attr.attr) + "<T extends Tag<T>>";
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) {
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");
final String attrName = interfaceNameSimple.toLowerCase();
final String paramName = attrName + "_";
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) {
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) {
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 {
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) {
sb.append("(final String ").append(paramName).append(") {")
.append("return self().attr(\"").append(attrName).append("\", ").append(paramName).append(");\n");
} else {
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);
}
}

View file

@ -0,0 +1,109 @@
package org.xbib.j2html.codegen.generators;
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 org.xbib.j2html.codegen.model.AttributesList;
public final class SpecializedTagClassCodeGenerator {
private static final String relPath = "tags/specialized";
public static void generate(final Path absPath, final boolean delete) throws IOException {
for (final String tag : TagCreatorCodeGenerator.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
);
if (!delete) {
Files.write(path, classString.getBytes());
}
}
for (final String tag : TagCreatorCodeGenerator.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)) {
Files.delete(path);
}
} else {
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(" "));
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");
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());
}
}

View file

@ -0,0 +1,130 @@
package org.xbib.j2html.codegen.generators;
import java.util.Arrays;
import java.util.List;
public final class TagCreatorCodeGenerator {
public static List<String> emptyTags() {
return Arrays.asList(
"area",
"base",
"br",
"col",
"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"
);
}
}

View file

@ -0,0 +1,22 @@
package org.xbib.j2html.codegen.model;
public final class AttrD {
public final String attr;
public final boolean hasArgument;
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;
}
}

View file

@ -0,0 +1,142 @@
package org.xbib.j2html.codegen.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public final class AttributesList {
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("action", true, "form"),
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"),
new AttrD("charset", true, "meta", "script"),
new AttrD("checked", false, "input"),
new AttrD("cite", true, "blockquote", "del", "ins", "q"),
new AttrD("cols", true, "textarea"),
new AttrD("colspan", true, "td", "th"),
new AttrD("content", true, "meta"),
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("dirname", true, "input", "textarea"),
new AttrD("disabled", false, "button", "fieldset", "input", "optgroup", "option", "select", "textarea"),
new AttrD("download", false, "a", "area"),
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("high", true, "meter"),
new AttrD("href", true, "a", "area", "base", "link"),
new AttrD("hreflang", true, "a", "area", "link"),
new AttrD("ismap", false, "img"),
new AttrD("kind", true, "track"),
new AttrD("label", true, "track", "option", "optgroup"),
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("oncanplay", true, "audio", "embed", "object", "video"),
new AttrD("oncanplaythrough", true, "audio", "video"),
new AttrD("oncuechange", true, "track"),
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("onhashchange", true, "body"),
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"),
new AttrD("onoffline", true, "body"),
new AttrD("ononline", true, "body"),
new AttrD("onpagehide", true, "body"),
new AttrD("onpageshow", true, "body"),
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("onsearch", true, "input"),
new AttrD("onseeked", true, "audio", "video"),
new AttrD("onseeking", true, "audio", "video"),
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("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("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("target", true, "a", "area", "base", "form"),
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")
);
}
}

View file

@ -0,0 +1,73 @@
package org.xbib.j2html.codegen;
import org.xbib.j2html.codegen.wattsi.WattsiSource;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.xbib.j2html.codegen.generators.TagCreatorCodeGenerator;
import org.xbib.j2html.codegen.wattsi.ElementDefinition;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
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.jupiter.api.Assertions.assertEquals;
public class CodeGeneratorComplianceTests {
private static WattsiSource specification;
@BeforeAll
public static 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
@Disabled
// 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(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(emptyList(), invalid);
}
}

View file

@ -0,0 +1,23 @@
package org.xbib.j2html.codegen;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.jupiter.api.Test;
public class GenerateTest {
@Test
public void generate() throws IOException {
try (InputStream inputStream = getClass().getResourceAsStream("/html.model")) {
String definitions = new String(inputStream.readAllBytes());
Model model = new Model();
Parser.parse(definitions, model);
Path dir = Paths.get("build/generated/sources/j2html");
Files.createDirectories(dir);
Generator.generate(dir, "j2html.tags.attributes", "j2html.tags.specialized", model);
}
}
}

View file

@ -0,0 +1,67 @@
package org.xbib.j2html.codegen;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import java.util.function.Consumer;
import org.mockito.Mockito;
import org.xbib.j2html.codegen.Parser;
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("", Mockito::verifyNoInteractions);
}
@Test
public void whitespace_has_no_events() {
verifyParsing(" \t\t\t\t", Mockito::verifyNoInteractions);
}
@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...");
});
}
}

View file

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

View file

@ -0,0 +1,11 @@
package org.xbib.j2html.codegen.wattsi;
public interface ElementDefinition {
String name();
boolean isSelfClosing();
boolean isObsolete();
}

View file

@ -0,0 +1,72 @@
package org.xbib.j2html.codegen.wattsi;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import javax.lang.model.element.Modifier;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
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) {
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());
}
}
private static String methodName(String prefix, String... words) {
String[] tmp = new String[words.length + 1];
tmp[0] = prefix;
System.arraycopy(words, 0, tmp, 1, words.length);
return methodName(tmp);
}
private static String methodName(String... words) {
String[] camelCase = new String[words.length];
camelCase[0] = words[0];
for (int i = 1; i < words.length; i++) {
camelCase[i] = capitalize(words[i]);
}
return String.join("", camelCase);
}
private static String capitalize(String word) {
return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase();
}
}

View file

@ -0,0 +1,190 @@
package org.xbib.j2html.codegen.wattsi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
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 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);
Elements obsoleteAttributes = doc.select("p:contains(The following attributes are obsolete) + dl");
obsoleteAttributes.select("dt > dfn[element-attr]").stream()
.map(WattsiAttribute::new)
.map(WattsiAttribute::reference)
.forEach(obsolete::add);
}
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) {
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;
}
}
}

File diff suppressed because it is too large Load diff

View 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]

5
j2html/build.gradle Normal file
View file

@ -0,0 +1,5 @@
dependencies {
testImplementation testLibs.velocity
testImplementation testLibs.junit.benchmarks
}

View file

@ -0,0 +1,9 @@
module org.xbib.j2html {
exports org.xbib.j2html;
exports org.xbib.j2html.attributes;
exports org.xbib.j2html.rendering;
exports org.xbib.j2html.tags;
exports org.xbib.j2html.tags.attributes;
exports org.xbib.j2html.tags.specialized;
exports org.xbib.j2html.utils;
}

View file

@ -0,0 +1,152 @@
package org.xbib.j2html;
import org.xbib.j2html.utils.CSSMin;
import org.xbib.j2html.utils.EscapeUtil;
import org.xbib.j2html.utils.Indenter;
import org.xbib.j2html.utils.JSMin;
import org.xbib.j2html.utils.Minifier;
import org.xbib.j2html.utils.TextEscaper;
import java.util.Collections;
public class Config {
/**
* Change this to configure text-escaping
* For example, to disable escaping, do <code>{@code Config.textEscaper = text -> text;}</code>
*/
public static TextEscaper textEscaper = EscapeUtil::escape;
/**
* Change this to configure css-minification.
* The default minifier is <a href="https://github.com/barryvan/CSSMin">CSSMin</a>
*/
public static Minifier cssMinifier = CSSMin::compressCss;
/**
* Change this to configure js-minification.
* The default minifier is a simple whitespace/newline stripper
*/
public static Minifier jsMinifier = JSMin::compressJs;
/**
* Change this to configure enable/disable closing empty tags
* The default is to NOT close them
*/
public static boolean closeEmptyTags = false;
private static final String FOUR_SPACES = " ";
/**
* Change this to configure indentation when rendering formatted html
* The default is four spaces
*/
public static Indenter indenter = (level, text) -> String.join("", Collections.nCopies(level, FOUR_SPACES)) + text;
private TextEscaper _textEscaper;
private Minifier _cssMinifier;
private Minifier _jsMinifier;
private boolean _closeEmptyTags;
private Indenter _indenter;
private Config(
TextEscaper _textEscaper,
Minifier _cssMinifier,
Minifier _jsMinifier,
boolean _closeEmptyTags,
Indenter _indenter
) {
this._textEscaper = _textEscaper;
this._cssMinifier = _cssMinifier;
this._jsMinifier = _jsMinifier;
this._closeEmptyTags = _closeEmptyTags;
this._indenter = _indenter;
}
/**
* A copy constructor.
*
* @param original The Config to copy fields from.
*/
private Config(Config original) {
this._textEscaper = original._textEscaper;
this._cssMinifier = original._cssMinifier;
this._jsMinifier = original._jsMinifier;
this._closeEmptyTags = original._closeEmptyTags;
this._indenter = original._indenter;
}
public TextEscaper textEscaper() {
return _textEscaper;
}
public Minifier cssMinifier() {
return _cssMinifier;
}
public Minifier jsMinifier() {
return _jsMinifier;
}
public boolean closeEmptyTags() {
return _closeEmptyTags;
}
public Indenter indenter() {
return _indenter;
}
public Config withTextEscaper(TextEscaper textEscaper){
Config copy = new Config(this);
copy._textEscaper = textEscaper;
return copy;
}
public Config withCssMinifier(Minifier cssMinifier){
Config copy = new Config(this);
copy._cssMinifier = cssMinifier;
return copy;
}
public Config withJsMinifier(Minifier jsMinifier){
Config copy = new Config(this);
copy._jsMinifier = jsMinifier;
return copy;
}
public Config withEmptyTagsClosed(boolean closeEmptyTags){
Config copy = new Config(this);
copy._closeEmptyTags = closeEmptyTags;
return copy;
}
public Config withIndenter(Indenter indenter){
Config copy = new Config(this);
copy._indenter = indenter;
return copy;
}
private static final Config DEFAULTS = new Config(
EscapeUtil::escape,
CSSMin::compressCss,
JSMin::compressJs,
false,
(level, text) -> String.join("", Collections.nCopies(level, FOUR_SPACES)) + text
);
public static Config defaults() {
return DEFAULTS;
}
public static Config global() {
return new Config(
textEscaper,
cssMinifier,
jsMinifier,
closeEmptyTags,
indenter
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,182 @@
package org.xbib.j2html.attributes;
import org.xbib.j2html.tags.Tag;
public abstract class Attr {
public static final String ACCEPT = "accept";
public static final String ACCEPT_CHARSET = "accept-charset";
public static final String ACCESSKEY = "accesskey";
public static final String ACTION = "action";
public static final String ALIGN = "align";
public static final String ALT = "alt";
public static final String ASYNC = "async";
public static final String AUTOCOMPLETE = "autocomplete";
public static final String AUTOFOCUS = "autofocus";
public static final String AUTOPLAY = "autoplay";
public static final String AUTOSAVE = "autosave";
public static final String BORDER = "border";
public static final String BUFFERED = "buffered";
public static final String CHALLENGE = "challenge";
public static final String CHARSET = "charset";
public static final String CHECKED = "checked";
public static final String CITE = "cite";
public static final String CLASS = "class";
public static final String COLOR = "color";
public static final String COLS = "cols";
public static final String COLSPAN = "colspan";
public static final String CONTENT = "content";
public static final String CONTENTEDITABLE = "contenteditable";
public static final String CONTEXTMENU = "contextmenu";
public static final String CONTROLS = "controls";
public static final String COORDS = "coords";
public static final String DATA = "data";
public static final String DATETIME = "datetime";
public static final String DEFAULT = "default";
public static final String DEFER = "defer";
public static final String DIR = "dir";
public static final String DIRNAME = "dirname";
public static final String DISABLED = "disabled";
public static final String DOWNLOAD = "download";
public static final String DRAGGABLE = "draggable";
public static final String DROPZONE = "dropzone";
public static final String ENCTYPE = "enctype";
public static final String FOR = "for";
public static final String FORM = "form";
public static final String FORMACTION = "formaction";
public static final String HEADERS = "headers";
public static final String HEIGHT = "height";
public static final String HIDDEN = "hidden";
public static final String HIGH = "high";
public static final String HREF = "href";
public static final String HREFLANG = "hreflang";
public static final String HTTP_EQUIV = "http-equiv";
public static final String ICON = "icon";
public static final String ID = "id";
public static final String IS = "is";
public static final String ISMAP = "ismap";
public static final String ITEMPROP = "itemprop";
public static final String KEYTYPE = "keytype";
public static final String KIND = "kind";
public static final String LABEL = "label";
public static final String LANG = "lang";
public static final String LANGUAGE = "language";
public static final String LIST = "list";
public static final String LOOP = "loop";
public static final String LOW = "low";
public static final String MANIFEST = "manifest";
public static final String MAX = "max";
public static final String MAXLENGTH = "maxlength";
public static final String MEDIA = "media";
public static final String METHOD = "method";
public static final String MIN = "min";
public static final String MULTIPLE = "multiple";
public static final String NAME = "name";
public static final String NOVALIDATE = "novalidate";
public static final String OPEN = "open";
public static final String OPTIMUM = "optimum";
public static final String PATTERN = "pattern";
public static final String PING = "ping";
public static final String PLACEHOLDER = "placeholder";
public static final String POSTER = "poster";
public static final String PRELOAD = "preload";
public static final String PUBDATE = "pubdate";
public static final String RADIOGROUP = "radiogroup";
public static final String READONLY = "readonly";
public static final String REL = "rel";
public static final String REQUIRED = "required";
public static final String REVERSED = "reversed";
public static final String ROLE = "role";
public static final String ROWS = "rows";
public static final String ROWSPAN = "rowspan";
public static final String SANDBOX = "sandbox";
public static final String SCOPE = "scope";
public static final String SCOPED = "scoped";
public static final String SEAMLESS = "seamless";
public static final String SELECTED = "selected";
public static final String SHAPE = "shape";
public static final String SIZE = "size";
public static final String SIZES = "sizes";
public static final String SLOT = "slot";
public static final String SPAN = "span";
public static final String SPELLCHECK = "spellcheck";
public static final String SRC = "src";
public static final String SRCDOC = "srcdoc";
public static final String SRCLANG = "srclang";
public static final String SRCSET = "srcset";
public static final String START = "start";
public static final String STEP = "step";
public static final String STYLE = "style";
public static final String SUMMARY = "summary";
public static final String TABINDEX = "tabindex";
public static final String TARGET = "target";
public static final String TITLE = "title";
public static final String TYPE = "type";
public static final String USEMAP = "usemap";
public static final String VALUE = "value";
public static final String WIDTH = "width";
public static final String WRAP = "wrap";
public static final String TRANSLATE = "translate";
public static ShortForm shortFormFromAttrsString(String attrs) {
if (!attrs.contains(".") && !attrs.contains(("#"))) {
throw new IllegalArgumentException("String must contain either id (#) or class (.)");
}
if (attrs.split("#").length > 2) {
throw new IllegalArgumentException("Only one id (#) allowed");
}
String id = "";
StringBuilder classes = new StringBuilder();
for (String attr : attrs.split("\\.")) {
if (attr.contains("#")) {
if (!attr.startsWith("#")) {
throw new IllegalArgumentException("# cannot be in the middle of string");
}
id = attr.replace("#", "");
} else {
classes.append(attr).append(" ");
}
}
return new ShortForm(id.trim(), classes.toString().trim());
}
public static <T extends Tag<T>> T addTo(T tag, Attr attr) {
if (null != attr)
attr.addTo(tag);
return tag;
}
public abstract <T extends Tag<T>> T addTo(T tag);
public static class ShortForm extends Attr {
String id;
String classes;
private ShortForm(String id, String classes) {
this.id = id;
this.classes = classes;
}
boolean hasId() {
return id != null && !"".equals(id);
}
boolean hasClasses() {
return classes != null && !"".equals(classes);
}
@Override
public <T extends Tag<T>> T addTo(T tag) {
if (hasId() && hasClasses()) {
return tag.withId(id).withClass(classes);
}
if (hasId()) {
return tag.withId(id);
}
if (hasClasses()) {
return tag.withClass(classes);
}
return tag;
}
}
}

View file

@ -0,0 +1,64 @@
package org.xbib.j2html.attributes;
import org.xbib.j2html.Config;
import org.xbib.j2html.rendering.TagBuilder;
import org.xbib.j2html.tags.Renderable;
import java.io.IOException;
public class Attribute implements Renderable {
private String name;
private String value;
public Attribute(String name, String value) {
this.name = name;
this.value = value;
}
public Attribute(String name) {
this.name = name;
this.value = null;
}
@Override
@Deprecated
public void renderModel(Appendable writer, Object model) throws IOException {
if (writer instanceof TagBuilder) {
if (name != null) {
if (value != null) {
((TagBuilder) writer).appendAttribute(name, value);
} else {
((TagBuilder) writer).appendBooleanAttribute(name);
}
}
} else {
if (name == null) {
return;
}
writer.append(' ');
writer.append(name);
if (value != null) {
writer.append("=\"");
writer.append(Config.textEscaper.escape(value));
writer.append('"');
}
}
}
public void render(TagBuilder builder, Object model) throws IOException {
// Maintain compatibility with classes that extend Attribute, for now...
renderModel(builder, model);
}
public String getName() {
return name;
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}

View file

@ -0,0 +1,182 @@
package org.xbib.j2html.rendering;
import org.xbib.j2html.Config;
import org.xbib.j2html.utils.TextEscaper;
import java.io.IOException;
/**
* Composes HTML without any extra line breaks or indentation.
*
* @param <T> The type of the Appendable to which HTML will be appended.
*/
public class FlatHtml<T extends Appendable> implements HtmlBuilder<T> {
/**
* Returns an HtmlBuilder that will generate flat HTML using
* Config defaults.
*
* @param out The Appendable to which HTML will be appended.
* @param <T> The type of the Appendable to which HTML will be appended.
* @return An HtmlBuilder for flat HTML.
*/
public static <T extends Appendable> FlatHtml<T> into(T out) {
return new FlatHtml<>(out, Config.defaults());
}
/**
* Returns an HtmlBuilder that will generate flat HTML using
* the given Config.
*
* @param out The Appendable to which HTML will be appended.
* @param config The Config which will specify text escapement, tag closing, etc.
* @param <T> The type of the Appendable to which HTML will be appended.
* @return An HtmlBuilder for flat HTML.
*/
public static <T extends Appendable> FlatHtml<T> into(T out, Config config) {
return new FlatHtml<>(out, config);
}
/**
* Returns an HtmlBuilder that will generate flat HTML in memory
* using Config defaults.
*
* @return An HtmlBuilder for flat HTML.
*/
public static FlatHtml<StringBuilder> inMemory() {
return into(new StringBuilder());
}
/**
* Returns an HtmlBuilder that will generate flat HTML in memory
* using the given Config.
* @param config The Config which will specify text escapement, tag closing, etc.
* @return An HtmlBuilder for flat HTML.
*/
public static FlatHtml<StringBuilder> inMemory(Config config) {
return into(new StringBuilder(), config);
}
private final T out;
private final TextEscaper textEscaper;
private final TagBuilder enclosingElementAttributes;
private final TagBuilder emptyElementAttributes;
private FlatHtml(T out, Config config) {
this.out = out;
this.textEscaper = config.textEscaper();
this.enclosingElementAttributes = new FlatTagBuilder(false);
this.emptyElementAttributes = new FlatTagBuilder(config.closeEmptyTags());
}
public T output() {
return out;
}
@Override
@Deprecated
public HtmlBuilder<T> append(CharSequence csq) throws IOException {
out.append(csq);
return this;
}
@Override
@Deprecated
public HtmlBuilder<T> append(CharSequence csq, int start, int end) throws IOException {
out.append(csq, start, end);
return this;
}
@Override
@Deprecated
public HtmlBuilder<T> append(char c) throws IOException {
out.append(c);
return this;
}
@Override
public TagBuilder appendStartTag(String name) throws IOException {
out.append("<").append(name);
return enclosingElementAttributes;
}
@Override
public HtmlBuilder<T> appendEndTag(String name) throws IOException {
out.append("</").append(name).append(">");
return this;
}
@Override
public TagBuilder appendEmptyTag(String name) throws IOException {
out.append("<").append(name);
return emptyElementAttributes;
}
@Override
public HtmlBuilder<T> appendEscapedText(String txt) throws IOException {
out.append(textEscaper.escape(txt));
return this;
}
@Override
public HtmlBuilder<T> appendUnescapedText(String txt) throws IOException {
out.append(txt);
return this;
}
private class FlatTagBuilder implements TagBuilder {
private final boolean closeTag;
private FlatTagBuilder(boolean closeTag) {
this.closeTag = closeTag;
}
@Override
public TagBuilder appendAttribute(String name, String value) throws IOException {
out.append(" ")
.append(name)
.append("=\"")
.append(textEscaper.escape(value))
.append("\"");
return this;
}
@Override
public TagBuilder appendBooleanAttribute(String name) throws IOException {
out.append(" ").append(name);
return this;
}
@Override
public HtmlBuilder<T> completeTag() throws IOException {
if (closeTag) {
out.append("/");
}
out.append(">");
return FlatHtml.this;
}
@Override
@Deprecated
public TagBuilder append(CharSequence csq) throws IOException {
out.append(csq);
return this;
}
@Override
@Deprecated
public TagBuilder append(CharSequence csq, int start, int end) throws IOException {
out.append(csq, start, end);
return this;
}
@Override
@Deprecated
public TagBuilder append(char c) throws IOException {
out.append(c);
return this;
}
}
}

View file

@ -0,0 +1,89 @@
package org.xbib.j2html.rendering;
import java.io.IOException;
/**
* Implementations of HtmlBuilder are wrappers around an
* Appendable, and support appending HTML-specific character
* sequences to that Appendable.
* <p>
* Note: HtmlBuilder extends Appendable for compatibility with
* previous version of this library. This extension will probably be
* removed in the future, so avoid relying on the deprecated methods
* of this interface.
*
* @param <T> The type of the Appendable. Used so that the
* same type can be returned to the caller, allowing
* for additional work to be done on the Appendable
* without the need for manual casting.
*/
public interface HtmlBuilder<T extends Appendable> extends Appendable {
/**
* Appends a start tag with the given name to the output. The
* returned TagBuilder is then used to append attributes
* and eventually complete the start tag.
*
* @param name The name of the start tag.
* @return An TagBuilder which can append attributes to the start tag.
* @throws IOException When the Appendable throws an IOException.
*/
TagBuilder appendStartTag(String name) throws IOException;
/**
* Appends an end tag with the given name to the output.
*
* @param name The name of the end tag.
* @return An HtmlBuilder that can continue appending HTML to the output.
* @throws IOException When the Appendable throws an IOException.
*/
HtmlBuilder<T> appendEndTag(String name) throws IOException;
/**
* Appends an empty tag with the given name to the output. The
* returned TagBuilder is then used to append attributes
* and eventually complete the empty tag.
*
* @param name The name of the empty tag.
* @return An TagBuilder which can append attributes to the empty tag.
* @throws IOException When the Appendable throws an IOException.
*/
TagBuilder appendEmptyTag(String name) throws IOException;
/**
* Appends escaped text to the output.
*
* @param txt The text to append.
* @return An HtmlBuilder that can continue appending HTML to the output.
* @throws IOException When the Appendable throws an IOException.
*/
HtmlBuilder<T> appendEscapedText(String txt) throws IOException;
/**
* Appends unescaped text to the output.
*
* @param txt The text to append.
* @return An HtmlBuilder that can continue appending HTML to the output.
* @throws IOException When the Appendable throws an IOException.
*/
HtmlBuilder<T> appendUnescapedText(String txt) throws IOException;
/**
* Returns the Appendable that was being wrapped.
*
* @return The original Appendable.
*/
T output();
@Override
@Deprecated
HtmlBuilder<T> append(CharSequence csq) throws IOException;
@Override
@Deprecated
HtmlBuilder<T> append(CharSequence csq, int start, int end) throws IOException;
@Override
@Deprecated
HtmlBuilder<T> append(char c) throws IOException;
}

View file

@ -0,0 +1,246 @@
package org.xbib.j2html.rendering;
import org.xbib.j2html.Config;
import org.xbib.j2html.utils.Indenter;
import org.xbib.j2html.utils.TextEscaper;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* Composes HTML with lines breaks and indentation between tags and text.
*
* @param <T> The type of the Appendable to which HTML will be appended.
*/
public class IndentedHtml<T extends Appendable> implements HtmlBuilder<T> {
/**
* Returns an HtmlBuilder that will generate indented HTML using
* Config defaults.
*
* @param out The Appendable to which HTML will be appended.
* @param <T> The type of the Appendable to which HTML will be appended.
* @return An HtmlBuilder for indented HTML.
*/
public static final <T extends Appendable> IndentedHtml<T> into(T out) {
return new IndentedHtml<>(out, Config.defaults());
}
/**
* Returns an HtmlBuilder that will generate indented HTML using
* the given Config.
*
* @param out The Appendable to which HTML will be appended.
* @param config The Config which will specify indentation, text escapement, tag closing, etc.
* @param <T> The type of the Appendable to which HTML will be appended.
* @return An HtmlBuilder for indented HTML.
*/
public static final <T extends Appendable> IndentedHtml<T> into(T out, Config config) {
return new IndentedHtml<>(out, config);
}
/**
* Returns an HtmlBuilder that will generate indented HTML in memory using
* Config defaults.
*
* @return An HtmlBuilder for indented HTML.
*/
public static final IndentedHtml<StringBuilder> inMemory() {
return into(new StringBuilder());
}
/**
* Returns an HtmlBuilder that will generate indented HTML in memory using
* the given Config.
*
* @param config The Config which will specify indentation, text escapement, tag closing, etc.
* @return An HtmlBuilder for indented HTML.
*/
public static final IndentedHtml<StringBuilder> inMemory(Config config) {
return into(new StringBuilder(), config);
}
private final T out;
private final Indenter indenter;
private final TextEscaper textEscaper;
private final TagBuilder enclosingElementAttributes;
private final TagBuilder emptyElementAttributes;
// Dealing with preformatted elements (pre and textarea) requires
// that we know what our parent elements are. To do that we use
// a stack; adding items as start tags are created, and removing them
// as those tags are closed. Determining whether or not we are
// currently rendering into a preformatted element is as simple as
// asking if any tags on the stack match a preformatted element name.
private final Deque<String> trace = new ArrayDeque<>();
private IndentedHtml(T out, Config config) {
this.out = out;
this.indenter = config.indenter();
this.textEscaper = config.textEscaper();
this.enclosingElementAttributes = new IndentedTagBuilder(false);
this.emptyElementAttributes = new IndentedTagBuilder(config.closeEmptyTags());
}
private boolean isContentSelfFormatting() {
return trace.contains("pre") || trace.contains("textarea");
}
private int lvl() {
return trace.size();
}
@Override
public TagBuilder appendStartTag(String name) throws IOException {
if (!isContentSelfFormatting()) {
out.append(indenter.indent(lvl(), ""));
}
trace.push(name);
out.append("<").append(name);
return enclosingElementAttributes;
}
@Override
public HtmlBuilder<T> appendEndTag(String name) throws IOException {
if (!name.equals(trace.peek())) {
throw new RuntimeException("Incorrect element closed: " + name + ". Expected: " + trace.peek());
}
if (!isContentSelfFormatting()) {
trace.pop();
out.append(indenter.indent(lvl(), ""));
} else {
trace.pop();
}
out.append("</").append(name).append(">");
if (!isContentSelfFormatting()) {
out.append("\n");
}
return this;
}
@Override
public TagBuilder appendEmptyTag(String name) throws IOException {
if (!isContentSelfFormatting()) {
out.append(indenter.indent(lvl(), ""));
}
out.append("<").append(name);
return emptyElementAttributes;
}
private void appendLines(String txt) throws IOException {
if (!isContentSelfFormatting()) {
String[] lines = txt.split("\n");
for (String line : lines) {
out.append(indenter.indent(lvl(), line)).append("\n");
}
} else {
out.append(txt);
}
}
@Override
public HtmlBuilder<T> appendEscapedText(String txt) throws IOException {
appendLines(textEscaper.escape(txt));
return this;
}
@Override
public HtmlBuilder<T> appendUnescapedText(String txt) throws IOException {
appendLines(txt);
return this;
}
@Override
public T output() {
return out;
}
@Override
@Deprecated
public HtmlBuilder<T> append(CharSequence csq) throws IOException {
out.append(csq);
return this;
}
@Override
@Deprecated
public HtmlBuilder<T> append(CharSequence csq, int start, int end) throws IOException {
out.append(csq, start, end);
return this;
}
@Override
@Deprecated
public HtmlBuilder<T> append(char c) throws IOException {
out.append(c);
return this;
}
private class IndentedTagBuilder implements TagBuilder {
private final boolean closeTag;
private IndentedTagBuilder(boolean closeTag) {
this.closeTag = closeTag;
}
@Override
public TagBuilder appendAttribute(String name, String value) throws IOException {
out.append(" ")
.append(name)
.append("=\"")
.append(textEscaper.escape(value))
.append("\"");
return this;
}
@Override
public TagBuilder appendBooleanAttribute(String name) throws IOException {
out.append(" ").append(name);
return this;
}
@Override
public HtmlBuilder<T> completeTag() throws IOException {
if (closeTag) {
out.append("/");
}
out.append(">");
if (!isContentSelfFormatting()) {
out.append("\n");
}
return IndentedHtml.this;
}
@Override
@Deprecated
public TagBuilder append(CharSequence csq) throws IOException {
out.append(csq);
return this;
}
@Override
@Deprecated
public TagBuilder append(CharSequence csq, int start, int end) throws IOException {
out.append(csq, start, end);
return this;
}
@Override
@Deprecated
public TagBuilder append(char c) throws IOException {
out.append(c);
return this;
}
}
}

View file

@ -0,0 +1,57 @@
package org.xbib.j2html.rendering;
import java.io.IOException;
/**
* Implementations of TagBuilder are used to append HTML tag
* attributes to an Appendable. TagBuilders are scoped to the
* creation and completion of a specific tag, and should not be used
* outside of that tag.
* <p>
* Note: TagBuilder extends Appendable for compatibility with
* previous version of this library. This extension will probably be
* removed in the future, so avoid relying on the deprecated methods
* of this interface.
*/
public interface TagBuilder extends Appendable {
/**
* Appends an key/value pair as an HTML attribute to the current tag.
*
* @param name The name of an attribute.
* @param value The value of an attribute.
* @return An TagBuilder which can continue appending attributes.
* @throws IOException When the Appendable throws an IOException.
*/
TagBuilder appendAttribute(String name, String value) throws IOException;
/**
* Appends a name, as a boolean HTML attribute to the current tag.
*
* @param name The name of the boolean attribute.
* @return An TagBuilder which can continue appending attributes.
* @throws IOException When the Appendable throws an IOException.
*/
TagBuilder appendBooleanAttribute(String name) throws IOException;
/**
* Appends any characters which are necessary to close the current tag,
* and returns an HtmlBuilder that can continue appending to the output.
*
* @return An HtmlBuilder that can continue appending HTML to the output.
* @throws IOException When the Appendable throws an IOException.
*/
HtmlBuilder<? extends Appendable> completeTag() throws IOException;
@Override
@Deprecated
TagBuilder append(CharSequence csq) throws IOException;
@Override
@Deprecated
TagBuilder append(CharSequence csq, int start, int end) throws IOException;
@Override
@Deprecated
TagBuilder append(char c) throws IOException;
}

View file

@ -0,0 +1,186 @@
package org.xbib.j2html.tags;
import org.xbib.j2html.Config;
import org.xbib.j2html.attributes.Attribute;
import org.xbib.j2html.rendering.TagBuilder;
import org.xbib.j2html.rendering.FlatHtml;
import org.xbib.j2html.rendering.HtmlBuilder;
import org.xbib.j2html.rendering.IndentedHtml;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class ContainerTag<T extends ContainerTag<T>> extends Tag<T> {
protected List<DomContent> children;
public ContainerTag(String tagName) {
super(tagName);
this.children = new ArrayList<>();
}
/**
* Appends a DomContent-object to the end of this element
*
* @param child DomContent-object to be appended
* @return itself for easy chaining
*/
public T with(DomContent child) {
if (this == child) {
throw new RuntimeException("Cannot append a tag to itself.");
}
if (child != null) {
// in some cases, like when using iff(), we ignore null children
children.add(child);
}
return self();
}
/**
* Call with-method based on condition
* {@link #with(DomContent child)}
*
* @param condition the condition to use
* @param child DomContent-object to be appended if condition met
* @return itself for easy chaining
*/
public T condWith(boolean condition, DomContent child) {
return condition ? this.with(child) : self();
}
/**
* Appends a list of DomContent-objects to the end of this element
*
* @param children DomContent-objects to be appended
* @return itself for easy chaining
*/
public T with(Iterable<? extends DomContent> children) {
if (children != null) {
for (DomContent child : children) {
this.with(child);
}
}
return self();
}
/**
* Call with-method based on condition
* {@link #with(java.lang.Iterable)}
*
* @param condition the condition to use
* @param children DomContent-objects to be appended if condition met
* @return itself for easy chaining
*/
public T condWith(boolean condition, Iterable<? extends DomContent> children) {
return condition ? this.with(children) : self();
}
/**
* Appends the DomContent-objects to the end of this element
*
* @param children DomContent-objects to be appended
* @return itself for easy chaining
*/
public T with(DomContent... children) {
for (DomContent child : children) {
with(child);
}
return self();
}
/**
* Appends the DomContent-objects in the stream to the end of this element
*
* @param children Stream of DomContent-objects to be appended
* @return itself for easy chaining
*/
public T with(Stream<DomContent> children) {
children.forEach(this::with);
return self();
}
/**
* Call with-method based on condition
* {@link #with(DomContent... children)}
*
* @param condition the condition to use
* @param children DomContent-objects to be appended if condition met
* @return itself for easy chaining
*/
public T condWith(boolean condition, DomContent... children) {
return condition ? this.with(children) : self();
}
/**
* Appends a Text-object to this element
*
* @param text the text to be appended
* @return itself for easy chaining
*/
public T withText(String text) {
return with(new Text(text));
}
/**
* Gets number of child nodes this tag element contains
*/
public int getNumChildren() {
return children.size();
}
/**
* Render the ContainerTag and its children, adding newlines before each
* child and using Config.indenter to indent child based on how deep
* in the tree it is
*
* @return the rendered and formatted string
*/
public String renderFormatted() {
try {
return render(IndentedHtml.into(new StringBuilder(), Config.global())).toString();
}catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
@Override
public <A extends Appendable> A render(HtmlBuilder<A> builder, Object model) throws IOException {
if (hasTagName()) {
TagBuilder tagBuilder = builder.appendStartTag(getTagName());
for(Attribute attribute : getAttributes()){
attribute.render(tagBuilder, model);
}
tagBuilder.completeTag();
}
for(DomContent child : children){
child.render(builder, model);
}
if(hasTagName()) {
builder.appendEndTag(getTagName());
}
return builder.output();
}
@Override
@Deprecated
public void renderModel(Appendable writer, Object model) throws IOException {
HtmlBuilder<?> builder = (writer instanceof HtmlBuilder)
? (HtmlBuilder<?>) writer
: FlatHtml.into(writer, Config.global());
render(builder, model);
}
}

View file

@ -0,0 +1,8 @@
package org.xbib.j2html.tags;
public abstract class DomContent implements Renderable {
@Override
public String toString() {
return render();
}
}

View file

@ -0,0 +1,31 @@
package org.xbib.j2html.tags;
public class DomContentJoiner {
public static UnescapedText join(CharSequence delimiter, boolean fixPeriodAndCommaSpacing, Object... stringOrDomObjects) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < stringOrDomObjects.length; i++) {
Object o = stringOrDomObjects[i];
if (o instanceof String) {
sb.append(((String) o).trim());
} else if (o instanceof DomContent) {
sb.append(((DomContent) o).render().trim());
} else if (o == null) {
//Discard null objects so iff/iffelse can be used with join
continue;
} else {
throw new RuntimeException("You can only join DomContent and String objects");
}
if (i < stringOrDomObjects.length-1) {
sb.append(delimiter);
}
}
String joined = sb.toString().trim();
if (fixPeriodAndCommaSpacing) {
joined = joined.replaceAll("\\s\\.", ".");
joined = joined.replaceAll("\\s\\,", ",");
}
return new UnescapedText(joined);
}
}

View file

@ -0,0 +1,42 @@
package org.xbib.j2html.tags;
import org.xbib.j2html.Config;
import org.xbib.j2html.attributes.Attribute;
import org.xbib.j2html.rendering.TagBuilder;
import org.xbib.j2html.rendering.FlatHtml;
import org.xbib.j2html.rendering.HtmlBuilder;
import java.io.IOException;
public class EmptyTag<T extends EmptyTag<T>> extends Tag<T> {
public EmptyTag(String tagName) {
super(tagName);
if (tagName == null) {
throw new IllegalArgumentException("Illegal tag name: null");
}
if ("".equals(tagName)) {
throw new IllegalArgumentException("Illegal tag name: \"\"");
}
}
@Override
public <A extends Appendable> A render(HtmlBuilder<A> builder, Object model) throws IOException {
TagBuilder attrs = builder.appendEmptyTag(getTagName());
for (Attribute attr : getAttributes()) {
attr.render(attrs, model);
}
attrs.completeTag();
return builder.output();
}
@Override
@Deprecated
public void renderModel(Appendable writer, Object model) throws IOException {
HtmlBuilder<?> builder = (writer instanceof HtmlBuilder)
? (HtmlBuilder<?>) writer
: FlatHtml.into(writer, Config.global());
render(builder, model);
}
}

View file

@ -0,0 +1,20 @@
package org.xbib.j2html.tags;
public interface IInstance<T> {
//to get the actual instance
// (every implementing class would have to implement: { return this; }
// public T self();
// this method shows up in autocomplete.
// this is really undesireable as it does not do anything.
@SuppressWarnings("unchecked")
default T self() {
//we know that the implementing class will supply
//its own type as the type argument.
//therefore every instance of IInstance can assume it
//is also of type T
return (T) this;
}
}

View file

@ -0,0 +1,44 @@
package org.xbib.j2html.tags;
import org.xbib.j2html.Config;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Scanner;
import static org.xbib.j2html.TagCreator.rawHtml;
import static org.xbib.j2html.TagCreator.script;
import static org.xbib.j2html.TagCreator.style;
public class InlineStaticResource {
public static ContainerTag<? extends Tag<?>> get(String path, TargetFormat format) {
String fileString = getFileAsString(path);
return switch (format) {
case CSS_MIN -> style().with(rawHtml(Config.cssMinifier.minify(fileString)));
case JS_MIN -> script().with(rawHtml(Config.jsMinifier.minify((fileString))));
case CSS -> style().with(rawHtml(fileString));
case JS -> script().with(rawHtml(fileString));
};
}
public static String getFileAsString(String path) {
try {
return streamToString(InlineStaticResource.class.getResourceAsStream(path));
} catch (Exception expected) {
try {
return streamToString(new FileInputStream(path));
} catch (Exception exception) {
throw new RuntimeException("Couldn't find file with path='" + path + "'");
}
}
}
private static String streamToString(InputStream inputStream) {
Scanner s = new Scanner(inputStream).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
public enum TargetFormat {CSS_MIN, CSS, JS_MIN, JS}
}

View file

@ -0,0 +1,81 @@
package org.xbib.j2html.tags;
import org.xbib.j2html.Config;
import org.xbib.j2html.rendering.FlatHtml;
import org.xbib.j2html.rendering.HtmlBuilder;
import java.io.IOException;
import java.io.UncheckedIOException;
public interface Renderable {
/**
* Render the Renderable and it's children using the supplied builder.
*
* @param builder A builder that can compose HTML elements.
* @param model A model object to provide data for children to render.
* @param <T> The type of the Appendable which HTML is being appended to.
* @return The Appendable to which HTML has been appended.
* @throws IOException
*/
default <T extends Appendable> T render(HtmlBuilder<T> builder, Object model) throws IOException {
// This method should be overridden by any internal classes.
// renderModel() is only being called to support backwards
// compatibility.
renderModel(builder, model);
return builder.output();
}
/**
* Render the Renderable and it's children using the supplied builder.
*
* @param builder A builder that can compose HTML elements.
* @param <T> The type of the Appendable to which HTML is being appended.
* @return The Appendable to which HTML has been appended.
* @throws IOException
*/
default <T extends Appendable> T render(HtmlBuilder<T> builder) throws IOException {
return render(builder, null);
}
/**
* Create a StringBuilder and use it to render the Renderable and it's
* children
*/
default String render() {
try {
return render(FlatHtml.into(new StringBuilder(), Config.global())).toString();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* Render the Renderable and it's children using the supplied writer
*
* @param writer the current writer
*/
@Deprecated
default void render(Appendable writer) throws IOException {
if (writer instanceof HtmlBuilder) {
render((HtmlBuilder<? extends Appendable>) writer);
} else {
render(FlatHtml.into(writer, Config.global()));
}
}
/**
* Render the Renderable and it's children using the supplied writer and a model.
*
* @param writer the current writer
* @param model a model object to provide data for children to render
*/
@Deprecated
default void renderModel(Appendable writer, Object model) throws IOException {
// This method is a placeholder to support any client classes
// which previously extended Renderable implementers, such as Tags.
// No internal classes should implement this method; except to support
// compatibility. Instead they should implement rendering with an HtmlBuilder.
throw new RuntimeException("Renderable.renderModel(Appendable writer, Object model) has been deprecated. Please use Renderable.render(HtmlBuilder<T> builder, Object model) instead.");
}
}

View file

@ -0,0 +1,213 @@
package org.xbib.j2html.tags;
import org.xbib.j2html.attributes.Attr;
import org.xbib.j2html.attributes.Attribute;
import java.util.ArrayList;
import java.util.Iterator;
public abstract class Tag<T extends Tag<T>> extends DomContent implements IInstance<T> {
private final String tagName;
private final ArrayList<Attribute> attributes;
protected Tag(String tagName) {
this.tagName = tagName;
this.attributes = new ArrayList<>();
}
public String getTagName() {
return this.tagName;
}
protected boolean hasTagName() {
return tagName != null && !tagName.isEmpty();
}
protected ArrayList<Attribute> getAttributes() {
return attributes;
}
/**
* Sets an attribute on an element
*
* @param name the attribute
* @param value the attribute value
*/
boolean setAttribute(String name, String value) {
if (value == null) {
return attributes.add(new Attribute(name));
}
for (Attribute attribute : attributes) {
if (attribute.getName().equals(name)) {
attribute.setValue(value); // update with new value
return true;
}
}
return attributes.add(new Attribute(name, value));
}
/**
* Sets a custom attribute
*
* @param attribute the attribute name
* @param value the attribute value
* @return itself for easy chaining
*/
public T attr(String attribute, Object value) {
setAttribute(attribute, value == null ? null : String.valueOf(value));
return self();
}
/**
* Adds the specified attribute. If the Tag previously contained an attribute with the same name, the old attribute is replaced by the specified attribute.
*
* @param attribute the attribute
* @return itself for easy chaining
*/
public T attr(Attribute attribute) {
Iterator<Attribute> iterator = attributes.iterator();
String name = attribute.getName();
if (name != null) {
// name == null is allowed, but those Attributes are not rendered. So we add them anyway.
while (iterator.hasNext()) {
Attribute existingAttribute = iterator.next();
if (existingAttribute.getName().equals(name)) {
iterator.remove();
}
}
}
attributes.add(attribute);
return self();
}
/**
* Sets a custom attribute without value
*
* @param attribute the attribute name
* @return itself for easy chaining
* @see Tag#attr(String, Object)
*/
public T attr(String attribute) {
return attr(attribute, null);
}
/**
* Call attr-method based on condition
* {@link #attr(String attribute, Object value)}
*
* @param condition the condition
* @param attribute the attribute name
* @param value the attribute value
* @return itself for easy chaining
*/
public T condAttr(boolean condition, String attribute, String value) {
return (condition ? attr(attribute, value) : self());
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Tag)) {
return false;
}
return ((Tag<?>) obj).render().equals(this.render());
}
/**
* Convenience methods that call attr with predefined attributes
*
* @return itself for easy chaining
*/
public T withClasses(String... classes) {
StringBuilder sb = new StringBuilder();
for (String s : classes) {
sb.append(s != null ? s : "").append(" ");
}
return attr(Attr.CLASS, sb.toString().trim());
}
/*
Tag.java contains all Global Attributes, Attributes which are
valid on all HTML Tags. Reference:
https://www.w3schools.com/tags/ref_standardattributes.asp
Attributes:
accesskey
class
contenteditable
data-*
dir
draggable
hidden
id
lang
spellcheck
style
tabindex
title
translate
*/
public T withAccesskey(String accesskey){ return attr(Attr.ACCESSKEY, accesskey); }
public T withClass(String className) { return attr(Attr.CLASS, className); }
public T isContenteditable(){ return attr(Attr.CONTENTEDITABLE, "true"); }
public T withData(String dataAttr, String value) { return attr(Attr.DATA + "-" + dataAttr, value); }
public T withDir(String dir) { return attr(Attr.DIR, dir); }
public T isDraggable(){ return attr(Attr.DRAGGABLE, "true"); }
public T isHidden() { return attr(Attr.HIDDEN, null); }
public T withId(String id) { return attr(Attr.ID, id); }
public T withIs(String element){ return attr(Attr.IS, element); }
public T withLang(String lang) { return attr(Attr.LANG, lang); }
public T withSlot(String name){ return attr(Attr.SLOT, name); }
public T isSpellcheck(){ return attr(Attr.SPELLCHECK, "true"); }
public T withStyle(String style) { return attr(Attr.STYLE, style); }
public T withTabindex(int index){ return attr(Attr.TABINDEX, index); }
public T withTitle(String title) { return attr(Attr.TITLE, title); }
public T isTranslate(){ return attr(Attr.TRANSLATE, "yes"); }
// ----- start of withCond$ATTR variants -----
public T withCondAccessKey(boolean condition, String accesskey){ return condAttr(condition, Attr.ACCESSKEY, accesskey); }
public T withCondClass(boolean condition, String className) { return condAttr(condition, Attr.CLASS, className); }
public T withCondContenteditable(boolean condition){ return attr(Attr.CONTENTEDITABLE, (condition)?"true":"false");}
public T withCondData(boolean condition, String dataAttr, String value) { return condAttr(condition, Attr.DATA + "-" + dataAttr, value); }
public T withCondDir(boolean condition, String dir) { return condAttr(condition, Attr.DIR, dir); }
public T withCondDraggable(boolean condition){ return attr(Attr.DRAGGABLE, (condition)?"true":"false"); }
public T withCondHidden(boolean condition) { return condAttr(condition, Attr.HIDDEN, null); }
public T withCondId(boolean condition, String id) { return condAttr(condition, Attr.ID, id); }
public T withCondIs(boolean condition, String element){ return condAttr(condition, Attr.IS, element); }
public T withCondLang(boolean condition, String lang) { return condAttr(condition, Attr.LANG, lang); }
public T withCondSlot(boolean condition, String name){ return condAttr(condition, Attr.SLOT, name); }
public T withCondSpellcheck(boolean condition){ return attr(Attr.SPELLCHECK, (condition)?"true":"false"); }
public T withCondStyle(boolean condition, String style) { return condAttr(condition, Attr.STYLE, style); }
public T withCondTabindex(boolean condition, int index){ return condAttr(condition, Attr.TABINDEX, index+""); }
public T withCondTitle(boolean condition, String title) { return condAttr(condition, Attr.TITLE, title); }
public T withCondTranslate(boolean condition){ return attr(Attr.TRANSLATE, (condition)?"yes":"no"); }
}

View file

@ -0,0 +1,33 @@
package org.xbib.j2html.tags;
import org.xbib.j2html.Config;
import org.xbib.j2html.rendering.FlatHtml;
import org.xbib.j2html.rendering.HtmlBuilder;
import java.io.IOException;
public class Text extends DomContent {
private final String text;
public Text(String text) {
this.text = text;
}
@Override
public <T extends Appendable> T render(HtmlBuilder<T> builder, Object model) throws IOException {
builder.appendEscapedText(String.valueOf(text));
return builder.output();
}
@Override
@Deprecated
public void renderModel(Appendable writer, Object model) throws IOException {
HtmlBuilder<?> builder = (writer instanceof HtmlBuilder)
? (HtmlBuilder<?>) writer
: FlatHtml.into(writer, Config.global());
render(builder, model);
}
}

View file

@ -0,0 +1,40 @@
package org.xbib.j2html.tags;
import org.xbib.j2html.Config;
import org.xbib.j2html.rendering.FlatHtml;
import org.xbib.j2html.rendering.HtmlBuilder;
import java.io.IOException;
public class UnescapedText extends DomContent {
private final String text;
public UnescapedText(String text) {
this.text = text;
}
@Override
public <T extends Appendable> T render(HtmlBuilder<T> builder, Object model) throws IOException {
builder.appendUnescapedText(String.valueOf(text));
return builder.output();
}
@Override
@Deprecated
public void renderModel(Appendable writer, Object model) throws IOException {
HtmlBuilder<?> builder = (writer instanceof HtmlBuilder)
? (HtmlBuilder<?>) writer
: FlatHtml.into(writer, Config.global());
render(builder, model);
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof UnescapedText)) {
return false;
}
return ((UnescapedText) obj).render().equals(this.render());
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IAccept<T extends Tag<T>> extends IInstance<T> {
default T withAccept(final String accept_) {
return self().attr("accept", accept_);
}
default T withCondAccept(final boolean enable, final String accept_) {
return enable ? self().attr("accept", accept_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IAction<T extends Tag<T>> extends IInstance<T> {
default T withAction(final String action_) {
return self().attr("action", action_);
}
default T withCondAction(final boolean enable, final String action_) {
return enable ? self().attr("action", action_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IAlt<T extends Tag<T>> extends IInstance<T> {
default T withAlt(final String alt_) {
return self().attr("alt", alt_);
}
default T withCondAlt(final boolean enable, final String alt_) {
return enable ? self().attr("alt", alt_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IAsync<T extends Tag<T>> extends IInstance<T> {
default T isAsync() {
return self().attr("async");
}
default T withCondAsync(final boolean enable) {
return enable ? self().attr("async") : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IAutocomplete<T extends Tag<T>> extends IInstance<T> {
default T isAutocomplete() {
return self().attr("autocomplete", "on");
}
default T withCondAutocomplete(final boolean enable) {
return enable ? self().attr("autocomplete", "on") : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IAutofocus<T extends Tag<T>> extends IInstance<T> {
default T isAutofocus() {
return self().attr("autofocus");
}
default T withCondAutofocus(final boolean enable) {
return enable ? self().attr("autofocus") : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IAutoplay<T extends Tag<T>> extends IInstance<T> {
default T isAutoplay() {
return self().attr("autoplay");
}
default T withCondAutoplay(final boolean enable) {
return enable ? self().attr("autoplay") : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface ICharset<T extends Tag<T>> extends IInstance<T> {
default T withCharset(final String charset_) {
return self().attr("charset", charset_);
}
default T withCondCharset(final boolean enable, final String charset_) {
return enable ? self().attr("charset", charset_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IChecked<T extends Tag<T>> extends IInstance<T> {
default T isChecked() {
return self().attr("checked");
}
default T withCondChecked(final boolean enable) {
return enable ? self().attr("checked") : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface ICite<T extends Tag<T>> extends IInstance<T> {
default T withCite(final String cite_) {
return self().attr("cite", cite_);
}
default T withCondCite(final boolean enable, final String cite_) {
return enable ? self().attr("cite", cite_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface ICols<T extends Tag<T>> extends IInstance<T> {
default T withCols(final String cols_) {
return self().attr("cols", cols_);
}
default T withCondCols(final boolean enable, final String cols_) {
return enable ? self().attr("cols", cols_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IColspan<T extends Tag<T>> extends IInstance<T> {
default T withColspan(final String colspan_) {
return self().attr("colspan", colspan_);
}
default T withCondColspan(final boolean enable, final String colspan_) {
return enable ? self().attr("colspan", colspan_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IContent<T extends Tag<T>> extends IInstance<T> {
default T withContent(final String content_) {
return self().attr("content", content_);
}
default T withCondContent(final boolean enable, final String content_) {
return enable ? self().attr("content", content_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IControls<T extends Tag<T>> extends IInstance<T> {
default T isControls() {
return self().attr("controls");
}
default T withCondControls(final boolean enable) {
return enable ? self().attr("controls") : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface ICoords<T extends Tag<T>> extends IInstance<T> {
default T withCoords(final String coords_) {
return self().attr("coords", coords_);
}
default T withCondCoords(final boolean enable, final String coords_) {
return enable ? self().attr("coords", coords_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IData<T extends Tag<T>> extends IInstance<T> {
default T withData(final String data_) {
return self().attr("data", data_);
}
default T withCondData(final boolean enable, final String data_) {
return enable ? self().attr("data", data_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IDatetime<T extends Tag<T>> extends IInstance<T> {
default T withDatetime(final String datetime_) {
return self().attr("datetime", datetime_);
}
default T withCondDatetime(final boolean enable, final String datetime_) {
return enable ? self().attr("datetime", datetime_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IDefault<T extends Tag<T>> extends IInstance<T> {
default T isDefault() {
return self().attr("default");
}
default T withCondDefault(final boolean enable) {
return enable ? self().attr("default") : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IDefer<T extends Tag<T>> extends IInstance<T> {
default T isDefer() {
return self().attr("defer");
}
default T withCondDefer(final boolean enable) {
return enable ? self().attr("defer") : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IDirname<T extends Tag<T>> extends IInstance<T> {
default T withDirname(final String dirname_) {
return self().attr("dirname", dirname_);
}
default T withCondDirname(final boolean enable, final String dirname_) {
return enable ? self().attr("dirname", dirname_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IDisabled<T extends Tag<T>> extends IInstance<T> {
default T isDisabled() {
return self().attr("disabled");
}
default T withCondDisabled(final boolean enable) {
return enable ? self().attr("disabled") : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IDownload<T extends Tag<T>> extends IInstance<T> {
default T isDownload() {
return self().attr("download");
}
default T withCondDownload(final boolean enable) {
return enable ? self().attr("download") : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IEnctype<T extends Tag<T>> extends IInstance<T> {
default T withEnctype(final String enctype_) {
return self().attr("enctype", enctype_);
}
default T withCondEnctype(final boolean enable, final String enctype_) {
return enable ? self().attr("enctype", enctype_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IFor<T extends Tag<T>> extends IInstance<T> {
default T withFor(final String for_) {
return self().attr("for", for_);
}
default T withCondFor(final boolean enable, final String for_) {
return enable ? self().attr("for", for_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IForm<T extends Tag<T>> extends IInstance<T> {
default T withForm(final String form_) {
return self().attr("form", form_);
}
default T withCondForm(final boolean enable, final String form_) {
return enable ? self().attr("form", form_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IFormaction<T extends Tag<T>> extends IInstance<T> {
default T withFormaction(final String formaction_) {
return self().attr("formaction", formaction_);
}
default T withCondFormaction(final boolean enable, final String formaction_) {
return enable ? self().attr("formaction", formaction_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IHeaders<T extends Tag<T>> extends IInstance<T> {
default T withHeaders(final String headers_) {
return self().attr("headers", headers_);
}
default T withCondHeaders(final boolean enable, final String headers_) {
return enable ? self().attr("headers", headers_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IHeight<T extends Tag<T>> extends IInstance<T> {
default T withHeight(final String height_) {
return self().attr("height", height_);
}
default T withCondHeight(final boolean enable, final String height_) {
return enable ? self().attr("height", height_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IHigh<T extends Tag<T>> extends IInstance<T> {
default T withHigh(final String high_) {
return self().attr("high", high_);
}
default T withCondHigh(final boolean enable, final String high_) {
return enable ? self().attr("high", high_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IHref<T extends Tag<T>> extends IInstance<T> {
default T withHref(final String href_) {
return self().attr("href", href_);
}
default T withCondHref(final boolean enable, final String href_) {
return enable ? self().attr("href", href_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IHreflang<T extends Tag<T>> extends IInstance<T> {
default T withHreflang(final String hreflang_) {
return self().attr("hreflang", hreflang_);
}
default T withCondHreflang(final boolean enable, final String hreflang_) {
return enable ? self().attr("hreflang", hreflang_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IIsmap<T extends Tag<T>> extends IInstance<T> {
default T isIsmap() {
return self().attr("ismap");
}
default T withCondIsmap(final boolean enable) {
return enable ? self().attr("ismap") : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IKind<T extends Tag<T>> extends IInstance<T> {
default T withKind(final String kind_) {
return self().attr("kind", kind_);
}
default T withCondKind(final boolean enable, final String kind_) {
return enable ? self().attr("kind", kind_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface ILabel<T extends Tag<T>> extends IInstance<T> {
default T withLabel(final String label_) {
return self().attr("label", label_);
}
default T withCondLabel(final boolean enable, final String label_) {
return enable ? self().attr("label", label_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IList<T extends Tag<T>> extends IInstance<T> {
default T withList(final String list_) {
return self().attr("list", list_);
}
default T withCondList(final boolean enable, final String list_) {
return enable ? self().attr("list", list_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface ILoop<T extends Tag<T>> extends IInstance<T> {
default T isLoop() {
return self().attr("loop");
}
default T withCondLoop(final boolean enable) {
return enable ? self().attr("loop") : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface ILow<T extends Tag<T>> extends IInstance<T> {
default T withLow(final String low_) {
return self().attr("low", low_);
}
default T withCondLow(final boolean enable, final String low_) {
return enable ? self().attr("low", low_) : self();
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.j2html.tags.attributes;
import org.xbib.j2html.tags.IInstance;
import org.xbib.j2html.tags.Tag;
public interface IMax<T extends Tag<T>> extends IInstance<T> {
default T withMax(final String max_) {
return self().attr("max", max_);
}
default T withCondMax(final boolean enable, final String max_) {
return enable ? self().attr("max", max_) : self();
}
}

Some files were not shown because too many files have changed in this diff Show more