78 Commits

Author SHA1 Message Date
dependabot[bot]
4b99683147 Bump jsoup from 1.14.3 to 1.15.3 in /code_gen
Bumps [jsoup](https://github.com/jhy/jsoup) from 1.14.3 to 1.15.3.
- [Release notes](https://github.com/jhy/jsoup/releases)
- [Changelog](https://github.com/jhy/jsoup/blob/master/CHANGES)
- [Commits](https://github.com/jhy/jsoup/compare/jsoup-1.14.3...jsoup-1.15.3)

---
updated-dependencies:
- dependency-name: org.jsoup:jsoup
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-01 23:55:56 +00:00
Scott Embler
38c08d4724 Updating README.md and website documents to match with release 1.6.0. (#214) 2022-06-30 17:49:14 -04:00
David
1c164a0773 [maven-release-plugin] prepare for next development iteration 2022-06-30 20:40:14 +02:00
David
dcc7258c8e [maven-release-plugin] prepare release j2html-1.6.0 2022-06-30 20:40:09 +02:00
Scott Embler
930ade9354 Upgrading several maven plugins. (#213)
- The maven-enforcer-plugin had an API incompatibility that might be fixed with a version upgrade.
- Several other plugins were out-of-date, and worth upgrading.
- Some plugins were not upgraded out of concern that they might break the build.
2022-06-30 08:12:09 +02:00
Scott Embler
d1c404d5db Web component support (#211)
* Foundational components for reading/processing Wattsi files into classes.

* Introducing tests for compliance with wattsi definitions.

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

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

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

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

- The GenerateTag class was created accidentally.  It does not have any representation in the HTML standard is should not be provided in this library.
2022-06-30 02:13:46 +02:00
Scott Embler
c2177d0584 Updated readme file with latest version number. (#206) 2022-02-02 13:34:48 -05:00
Scott Embler
5eb202b0a9 Updated html files for website. (#205) 2022-01-24 10:57:48 -05:00
Scott Embler
4bda3635ab Website update 1.5.0 release (#204)
* Updated new items to include 1.5.0.

- Adding short upgrade guide for incompatibilities between 1.4.0 and 1.5.0.
2022-01-23 22:04:46 -05:00
David (javalin.io)
f083b2cc9c Update and rename README to README.md 2022-01-23 22:31:21 +01:00
Scott Embler
9ad0e428c1 Fixes #200. Adds support for the picture tag. (#201) 2021-10-03 18:43:33 -04:00
Scott Embler
83866be83f Resolves #194. Introduces Revapi into the build process. (#195)
- Switching to verify goal in main.yml.
2021-06-17 10:17:50 -04:00
David
5135cffc3f Bump website to 1.5.0 2021-06-13 23:10:30 +02:00
David
09e08f3465 [maven-release-plugin] prepare for next development iteration 2021-06-13 23:02:20 +02:00
David
26ba4c3cae [maven-release-plugin] prepare release j2html-1.5.0 2021-06-13 23:02:14 +02:00
David
dc676b767f bump release plugin 2021-06-13 23:00:16 +02:00
David
07dcbb3ec2 specify pom location for release plugin 2021-06-13 22:11:29 +02:00
Scott Embler
b18459c66f Bifunction each (#191)
Adding TagCreator.each() method to provide access to the collection index.
2021-06-13 16:03:04 -04:00
David
aa8fb7c763 format java code in website project 2021-06-12 22:55:23 +02:00
David
c4f2429ec1 website readme 2021-06-12 22:45:00 +02:00
Scott Embler
a5aaeff22f Merge pull request #190 from obecker/remove-generated
Change package for generated tags
2021-06-12 16:30:49 -04:00
David
f4a179160c move website into repo 2021-06-12 22:19:38 +02:00
Oliver Becker
787035f6b1 Change package for generated tags
- move all generated tag classes from j2html.tags.specialized.generated to j2html.tags.specialized
  since they are now only generated tags and in the long run there should be no difference for a user of j2html whether the classes have been generated or not
2021-06-12 22:13:52 +02:00
Scott Embler
8b08968a6b Merge pull request #187 from obecker/htmltag
Remove manual tags, use only generated tags
2021-06-12 15:14:01 -04:00
Oliver Becker
56f61e421f Remove manual tags, use only generated tags
- change html, head, body from manual to generated tags
- restore compatibility with j2html 1.4.0
- fix https://github.com/tipsy/j2html/issues/185
2021-06-12 20:49:02 +02:00
Scott Embler
afd00597f7 Adding Automatic-Module-Name to manifest to support JDK 9+. (#183)
* Adding Automatic-Module-Name to manifest to support JDK 9+.

- Using reverse-DNS naming convention for module.
2021-06-08 12:23:20 -04:00
Scott Embler
8cc0b7c2c7 Merge pull request #184 from obecker/contributing-fix
Adjust CONTRIBUTING.md to the latest code changes
2021-06-07 18:25:58 -04:00
Oliver Becker
89f0194be0 Adjust CONTRIBUTING.md to the latest code changes
IInstance.get() has been renamed to self() in https://github.com/tipsy/j2html/pull/180
2021-06-07 17:43:35 +02:00
Scott Embler
1079a01750 Merge pull request #182 from obecker/each-fix
Remove calls to render() during tree construction.
2021-06-05 13:42:53 -04:00
Oliver Becker
f89957eeab Fix implementation flaw that called render() during tree construction
* Enables the new render capabilities also for content that was constructed with
  - TagCreator.each(Map, Function) and
  - TagCreator.each(Map, BiFunction)
* Adds convenience method TagCreator.each(DomContent...)
2021-06-04 15:57:16 +02:00
Scott Embler
c07facd490 Merge pull request #181 from sembler/fix_style_and_script_escaping
Treating text as unescaped when using TagCreator methods for style and script elements.
2021-06-03 10:24:45 -04:00
Scott.Embler
1d43a25457 Treating text as unescaped when using TagCreator methods for style and script elements.
- Replicating PR #152, but avoiding conflicts and using more current conventions.
2021-06-02 13:32:37 -04:00
sagesmith-wf
2ecd734239 Make join method work as expected (#168)
- Avoids appending a delimiter to the end of the joined text.
- Add test to verify changes.
2021-06-02 10:11:44 -04:00
Scott Embler
8b2669cab1 Merge pull request #180 from obecker/polish-code
Minor code improvements
2021-05-21 19:39:02 -04:00
Oliver Becker
8c0d03e200 Minor code improvements
* add compiler flag -Xlint:all, resolve all warnings about unchecked or raw generics
* rename IInstance.get() to IInstance.self() because it better reflects its intention
* move MainTag from manual to generated tags
* remove unnecessary type casts in TagCreator
2021-05-21 12:36:51 +02:00
Scott Embler
15702ac0d7 Extracted rendering functionality into implementations of HtmlBuilder. (#179)
* Extracted rendering functionality into implementations of HtmlBuilder.

- Two HtmlBuilders are implemented, FlatHtml and IndentedHtml.  Each offers the equivalent output as ContainerTag.render() and ContainerTag.renderFormatted.
- Existing implementations of DomContent/Renderable were updated to support HtmlBuilder where possible.
- Attribute was altered to support the use of TagBuilder.
- Config was altered to allow instances to be created, which can be passed into factory methods for the HtmlBuilders. Config.defaults() can be used for library defaults, while Config.global() can be used for current static configuration.
- Retained compatibility with previous library version as much as possible. See RenderingCompatabilityTest.

* Fixing unchecked or unsafe operations

* Removing unnecessary @param from javadoc.

* Improving casting of HtmlBuilders in implementations of DomContent (tags, text, etc.).

* Preventing NPEs when text is null.

Co-authored-by: Oliver Becker <ob@obqo.de>
2021-05-20 10:47:06 -04:00
Scott Embler
fd41cccfe2 Merge pull request #176 from obecker/intellij
Add .idea folder to .gitignore for developers using IntelliJ IDEA
2021-04-25 10:11:57 -04:00
Oliver Becker
31a565235e Add .idea folder to .gitignore for developers using IntelliJ IDEA 2021-04-25 10:32:23 +02:00
Oliver Becker
da76ee0d2b Fix indent of tags without tag name when using renderFormatted() (#174)
https://github.com/tipsy/j2html/issues/173

Off topic: Set execute flag on mvnw
2021-04-24 21:52:50 +02:00
Scott Embler
4a4c6314d8 Minor restructuring of Tag internals, increased argument validation, and tests to clarify current behavior. (#175)
Co-authored-by: Scott.Embler <scott.embler@noaa.gov>
2021-04-24 11:37:00 +02:00
pointbazaar
be626b6b96 completely separate the generated interfaces from manual ones and completely separate the manual tags from the generated ones. Also wipe the directories for code generation before code generation (#167) 2020-10-20 10:52:38 +02:00
dependabot[bot]
226cfb9a28 Bump junit from 4.11 to 4.13.1 in /code_gen (#166)
Bumps [junit](https://github.com/junit-team/junit4) from 4.11 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.11.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.11...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-17 10:25:29 +02:00
pointbazaar
acf6bff72a Split j2html in 2 projects, "code_gen" and "library" (Issue #161) (#165)
* split the project in 2, to make it easier to iterate on the code generation without breaking the build. This could also help reduce the compiled size of the library.

* update CONTRIBUTING.md

* try to fix github workflow
2020-10-17 10:23:57 +02:00
dependabot[bot]
07427c1433 Bump junit from 4.12 to 4.13.1 (#164)
Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-13 15:10:36 +02:00
lambdaupb
7d98d4f2bd Escape Attribute on render instead of in constructor. Fixes #160 (#162)
Also initialize EscapeUtil StringBuilder with input length to avoid growing copying.
2020-09-24 08:50:01 +02:00
lambdaupb
94ad6e29a3 Use one Appendable instance that is passed down for all rendering (#158) 2020-09-23 20:53:32 +02:00
pointbazaar
9338efc7cd Add CONTRIBUTING.md (depends on PR156) (#157)
* start on CONTRIBUTING.md

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md
2020-08-31 16:24:44 +02:00
pointbazaar
312cabe69c Implementation of Attributes-per-Tag via Interfaces (#156) 2020-08-31 16:24:00 +02:00
pointbazaar
f091000bbe first iteration of the attribute per tag POC (#154)
* create HtmlTag, BodyTag, HeadTag. hopefully without too large diffs this time.

* add some forgotten variants

* remove 'final' on some methods changed during the last 2 commits, to make it more consistent with the rest of the code

* remove <head>,<body>,<html> from TagCreatorCodeGenerator
2020-08-16 10:39:13 +02:00
David
2ae40f9e6c Update README.md 2020-08-15 17:38:58 +02:00
David
dd2079a136 add github ci workflow 2020-08-15 17:35:47 +02:00
David
b0cf8e8c6f add maven wrapper 2020-08-15 17:34:50 +02:00
David
793dcca03c remove travis file 2020-08-15 17:32:54 +02:00
pointbazaar
379ddb82bf Remove test dependency lombok
* lombok was almost never used. also gave me errors on 'mvn install' on ubuntu 20.04 https://github.com/rzwitserloot/lombok/issues/1651 . unless there are plans to use it more extensively, maybe it is best to remove it, to clean up the dependencies and enable me (and maybe others) to build without errors.

* fix Employee.java without lombok
2020-08-15 10:31:25 +02:00
Arne Zelasko
8316035854 add step attribute (#150) 2020-05-19 19:10:21 +02:00
b-gyula
1fcecb961e #145 override toString calling render() allowing inline usage: (#146)
```
String content = "Some content already generated";
content += tr(th("Name"),th("Desc")) + "some other content";
```
2020-02-06 12:28:01 +01:00
Jamie Mansfield
5c9d62cdf3 bug-fix: Children of non-formatted tags (#139)
Resolves GH-138.
2019-09-14 00:29:55 +02:00
David
c31badd3e4 Update .travis.yml 2019-09-13 23:46:39 +02:00
labkey-matthewb
629038303c remove private constructor from Attr (#134) 2019-06-04 20:49:35 +02:00
mbellew
0f30bc9d9a generalize addTo() to enable other attr helpers besides ShortForm (#133)
* small changes to make for easier experimentation

* generalize addTo() to enable other attr helpers besides ShortForm
2019-05-31 23:28:56 +02:00
mbellew
ae65127dcc small changes to make for easier experimentation (#132) 2019-05-31 23:20:10 +02:00
David
dae578a523 Bump version in readme 2019-01-24 14:49:40 +01:00
David
b89435ca7c Cleanup after tests 2019-01-24 14:49:13 +01:00
David
3deb6b4ddb [maven-release-plugin] prepare for next development iteration 2019-01-24 14:43:12 +01:00
David
023922a7db [maven-release-plugin] prepare release j2html-1.4.0 2019-01-24 14:42:55 +01:00
David
00c0461669 Auto format project 2019-01-24 14:24:21 +01:00
Robin Karlsson
cd6e0084ef Add Stream<DomContent> variants of each and with (#118) 2018-05-20 21:45:36 +02:00
Moandji Ezana
764b5d7759 Add TagCreator::each(Map, BiFunction) (#115) 2018-05-07 00:18:36 +02:00
David
d9c4963ff0 bump version 2018-05-01 12:29:02 +02:00
David
3ec15d28a5 woops 2018-05-01 12:28:13 +02:00
Moandji Ezana
f1680464d1 Do not indent textarea contents (#113)
* Do not indent textarea contents.

Fixes #102

* Restore import style

* Handle <pre> and TagCreator::each
2018-05-01 02:06:45 +02:00
Matthias
f497b5c8b5 support osgi meta data (#112) 2018-04-17 23:54:31 +02:00
Matthias
f212895eb2 Let each() take Map in addition to Collection (#110)
* add support for maps for 'each' method.

* add missing imports.
2018-04-16 20:11:37 +02:00
Rupert Madden-Abbott
62cafb9b31 Allow nulls in join() (fix #109)
* Discard nulls in join.

* Add test.
2018-04-16 20:07:11 +02:00
David
af7c986dd6 reorder asserts in each/fitler test 2018-02-05 01:36:46 +01:00
David
3aaab8b0fe unignore tests 2018-02-05 01:34:45 +01:00
David
cf9558b6ab fix formatting 2018-02-05 01:14:29 +01:00
Paul N. Baker
f87f9d8647 Overloaded method of iff for java8 Optional<>'s. (Includes test) (#103)
Using an optional as a condition is really tricky. Even if the optional is not present, the right value of ifValue must be evaluated eagerly in order to be passed to the method. Passing a lambda function for ifFunction allows the value to be evaluated lazily. This avoids the need to manually map and "elseGet" a null value to preserve current functionality.
2018-01-17 22:10:21 +01:00
410 changed files with 139974 additions and 3602 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
text eol=crlf

28
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Test all JDKs on all OSes
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
java_version: [1.8, 9, 10, 11, 12, 13]
os: [windows-latest, macOS-latest, ubuntu-latest]
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Set up JDK ${{ matrix.java_version }}
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.java_version }}
- name: Make Maven Wrapper executable
if: contains(matrix.os, 'win') == false
run: chmod +x ./mvnw
- name: Build with Maven
run: ./mvnw verify --file library/pom.xml --batch-mode
env:
MAVEN_OPTS: -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn

93
.gitignore vendored
View File

@@ -1,101 +1,14 @@
# Created by https://www.gitignore.io
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Windows ###
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### Java ###
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
### Eclipse ###
.classpath
.project
.settings/
buildNumber.properties
### IntelliJ ###
.idea/

117
.mvn/wrapper/MavenWrapperDownloader.java vendored Normal file
View File

@@ -0,0 +1,117 @@
/*
* Copyright 2007-present the original author or 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
*
* 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.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

2
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

View File

@@ -1,8 +0,0 @@
language: java
jdk:
- oraclejdk8
sudo: false
addons:
apt:
packages:
- oracle-java8-installer

153
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,153 @@
# Contributing
This Project is currently accepting Contributions.
Have a look at the Issues for Inspiration.
## Requirements
- JDK >= 1.8 (see pom.xml)
- Maven
## Project Goals
As stated in the **README**, the Goal of this Project is to enjoy typesafe, performant HTML generation in Java.
For preventing performance regressions, there are Tests.
## Coding Style
There is a `.editorconfig` and a `eclipse_formatting_profile.xml`
What sticks out is that end of line is CRLF `'\r\n'` for this Project.
This means that if you're on Linux `'\n'`, you have to configure git to handle this
correctly so that you have the correct EOL in your working directory,
and the EOL is also correct in the repository itself.
For this purpose, j2html has a `.gitattributes` file.
[Guide to configuring EOL with git](https://docs.github.com/en/github/using-git/configuring-git-to-handle-line-endings)
If you are on Windows, there should be no Problems.
### Reformatting of generated Java Code
As this Projects makes use of Code-Generation techniques in order to generate a more typesafe API without too much manual Work,
there is the `code_gen/` directory which contains everything needed to generate the code.
For simplicity (and also to avoid extra dependencies), they do not format the code correctly.
So if you change the Code-Generation Code, you may need to reformat the generated files to fit the Coding Style.
## Contribution Workflow
The workflow (most of the time) consists of:
- Comment (on Issue or PR) to find out what needs Work
- Get Feedback on your Ideas
- Fork this repo
- Open a PR
- Adjust the PR until it is merged or discarded
## Project Architecture
### library/src/main/java/j2html/TagCreator.java
This is **the** central class in J2HTML. It provides the methods
for users of J2HTML to generate all HTML Tags.
It contains methods like
```
public static HtmlTag html(DomContent... dc) {
return new HtmlTag().with(dc);
}
```
which can be used in Projects using this dependency as
```
html(
head(
script("https://example.com/my/js/files.js")
),
body(
div(
h1("Hello World")
).withClasses("container")
)
)
```
### How are the different HTML Tags implemented?
Each HTML Tag has it's own class, which makes it possible for each Tag to have
the correct Attributes and Methods to set those Attributes.
The classes are located in `library/src/main/java/j2html/tags/specialized` and follow the naming convention `tag_name + 'Tag.java'`, e.g. `BodyTag.java`.
Notice that the first letter of the Tag is in uppercase.
Each Tag-specific class `implements` interfaces which correspond to the Attributes that can be set on these Tags.
For Reference which Tags support which Attributes, see [HTML Attribute Reference](https://www.w3schools.com/tags/ref_attributes.asp).
For Example, `ButtonTag` might implement `IType<ButtonTag>` which says it can have an Attribute `type`, which may later show up like `<button type="submit"></button>`.
### How are the Attributes of HTML Tags implemented?
Each Attribute has it's own interface in `src/main/java/j2html/tags/attributes/` and follows the naming convention `"I" + attribute_name + '.java'`, e.g. `IAccept.java`. Notice that the first letter of the Attribute is in uppercase.
Dissecting `IAccept.java`:
```
public interface IAccept<T extends Tag> extends IInstance<T> {
default T withAccept(final String accept_) {
return self().attr("accept", accept_);
}
default T withCondAccept(final boolean enable, final String accept_) {
if (enable) {
self().attr("accept", accept_);
}
return self();
}
}
```
As you can see, **IAccept** extends `IInstance<T>` which provides only the `self()` Method to access an instance of type `T`.
All attribute-specific interfaces extend `IInstance<T>`.
```
public interface IInstance<T> {
default T self() { return (T) this; }
}
```
`IInstance<T>` is cheating the type system because `self()` returns an instance of type `T`, but the implementing class
technically does not have to supply it's own type as the type argument. But by convention, in this Project, the implementing class
always supplies it's own type as the type argument.
But in `default` methods in interfaces there is AFAIK no way to obtain the type of the class that is implementing the interface.
If you find a way, that would be a great PR.
### Special classes/interfaces besides TagCreator.java
There are 3 classes which contain code-generating methods in `code_gen/src/main/java/j2html_codegen/generators/`:
- `AttributeInterfaceCodeGenerator.java` (generating the interfaces for the attributes)
- `SpecializedTagClassCodeGenerator.java` (generating the classes for the tags)
- `TagCreatorCodeGenerator.java` (generating some contents of `TagCreator.java`)
### Other special classes / interfaces in J2HTML
- **Tag.java** is the base class for every tag and extends DomContent
- **EmptyTag.java** is the base class for all Tags which have no contents
- **ContainerTag.java** is the base class for all Tags which can contain other tags
- **DomContent.java**
### How is the Code generation for the Attribute specific Interfaces parameterized?
Attributes differ in their 'type' . Some of them can be set with numbers (which are converted into strings in the html).
Others can only be set or not set, others still have 3 states: set, unset, and not present.
To model these propertise, a single Attribute can be described by an instance of **AttrD.java**.
`library/src/main/java/j2html/tags/generators/AttributesList.java` contains the different Attributes, their properties,
and the Tags they can be set on. It is the starting point for adding new Attributes and customizing their properties.

View File

@@ -1,4 +1,4 @@
![](https://img.shields.io/travis/tipsy/j2html.svg)
[![Workflow](https://github.com/tipsy/j2html/workflows/Test%20all%20JDKs%20on%20all%20OSes/badge.svg)](https://github.com/tipsy/j2html/actions)
![](https://img.shields.io/github/license/tipsy/j2html.svg)
![](https://img.shields.io/maven-central/v/com.j2html/j2html.svg)
@@ -13,12 +13,12 @@ The project webpage is [j2html.com](http://j2html.com).
<dependency>
<groupId>com.j2html</groupId>
<artifactId>j2html</artifactId>
<version>1.2.2</version>
<version>1.6.0</version>
</dependency>
```
### Or the gradle dependency
```
compile 'com.j2html:j2html:1.2.2'
compile 'com.j2html:j2html:1.6.0'
```
### Import TagCreator and start building HTML

101
code_gen/.gitignore vendored Normal file
View File

@@ -0,0 +1,101 @@
# Created by https://www.gitignore.io
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Windows ###
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### Java ###
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
### Eclipse ###
.classpath
.project
.settings/
buildNumber.properties

85
code_gen/pom.xml Normal file
View File

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

View File

@@ -0,0 +1,33 @@
package j2html_codegen;
import j2html_codegen.generators.AttributeInterfaceCodeGenerator;
import j2html_codegen.generators.SpecializedTagClassCodeGenerator;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
public final class App
{
public static void main( String[] args )
{
final Path relPath = Paths.get("../library/src/main/java/j2html/");
final Path absPath = relPath.toAbsolutePath();
System.out.println("writing in "+absPath);
//decide if the files should be
//deleted or generated
final boolean delete = false;
try {
AttributeInterfaceCodeGenerator.generate(absPath, delete);
SpecializedTagClassCodeGenerator.generate(absPath, delete);
//TagCreatorCodeGenerator.print();
} catch (IOException e) {
e.printStackTrace();
}
//don't forget to auto-reformat the generated code.
}
}

View File

@@ -0,0 +1,16 @@
package j2html_codegen;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public final class GeneratorUtil {
public static final void deleteAllFilesInDir(final Path dir) throws IOException {
for(final File file : dir.toFile().listFiles()){
System.out.println("deleting " + file.toPath());
Files.delete(file.toPath());
}
}
}

View File

@@ -0,0 +1,184 @@
package j2html_codegen.generators;
import j2html_codegen.GeneratorUtil;
import j2html_codegen.model.AttrD;
import j2html_codegen.model.AttributesList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public final class AttributeInterfaceCodeGenerator {
private static final String relPath = "tags/attributes/";
public static void generate(final Path absPath, final boolean delete) throws IOException {
//delete all files in the directory for fresh generation
final Path dir = Paths.get(absPath.toString(),relPath);
GeneratorUtil.deleteAllFilesInDir(dir);
for (final AttrD attr : AttributesList.attributesDescriptive()) {
final Path path = makePath(attr.attr, absPath);
final String interfaceName = interfaceNameFromAttribute(attr.attr)+"<T extends Tag<T>>";
/*
IFormAction<T extends Tag<T>> extends IInstance<T>
default T withFormAction(String formAction){
return self().attr("formaction", formAction);
}
*/
final String interfaceStr = getInterfaceTemplate(
interfaceName,
Optional.of("IInstance<T>"),
Arrays.asList("j2html.tags.Tag","j2html.tags.IInstance"),
interfaceNameFromAttribute(attr.attr).substring(1),
attr
);
if (!delete) {
System.out.println("writing to "+path);
Files.write(path, interfaceStr.getBytes());
}
}
}
private static String getPackage(){
return "package j2html.tags.attributes;\n";
}
private static String makeReturnTypeAndMethodName(final String name){
return "default "+ "T "+name;
}
private static String getInterfaceTemplate(
final String interfaceName,
final Optional<String> optExtends,
final List<String> imports,
final String interfaceNameSimple,
final AttrD attrD
){
final StringBuilder sb = new StringBuilder();
sb.append(getPackage());
sb.append("\n");
for(String importName : imports){
sb.append("import ").append(importName).append(";\n");
}
sb.append("\n");
sb.append("public interface ")
.append(interfaceName);
optExtends.ifPresent(ext -> sb.append(" extends ").append(ext).append(" "));
sb.append(" {\n");
//interface contents
/*
IFormAction<T extends Tag> extends IInstance<T>
default T withFormAction(String formAction){
return self().attr("formaction", formAction);
}
*/
//IMPORTANT: '_' added as suffix to mitigate problems
//where attributes are java keywords. Just to make it consistent and avoid special cases.
final String attrName = interfaceNameSimple.toLowerCase();
final String paramName = attrName+"_";
//depending on if the attribute has an argument or not,
//generate methods according to the convention in Tag.java
// arg -> with$ATTR(arg), withCond$ATTR(condition, arg)
// no arg -> is$ATTR(), withCond$ATTR(condition)
//append the 'with$ATTR' method
writeAttributeMethod(interfaceNameSimple, attrD, sb, attrName, paramName);
writeAttributeMethodCond(interfaceNameSimple, attrD, sb, attrName, paramName);
sb.append("}\n");
return sb.toString();
}
private static void addAttributeNoArg(final StringBuilder sb, final String attrName){
//generate the code to add an attribute without an argument
//there are some special attributes
//which do take an argument, but where the argument
//is boolean (meaning on/off, yes/no and the like)
sb.append("self().attr(\"");
if (attrName.equals("autocomplete")){
sb.append(attrName).append("\",\"on\"");
} else {
sb.append(attrName).append("\"");
}
sb.append(");\n");
}
private static void writeAttributeMethodCond(String interfaceNameSimple, AttrD attrD, StringBuilder sb, String attrName, String paramName) {
sb.append(makeReturnTypeAndMethodName("withCond"+interfaceNameSimple));
if(attrD.hasArgument){
//add a variant where you can specify the argument
sb.append("(final boolean enable, final String ").append(paramName).append(") {");
sb.append("if (enable){\n");
sb.append("self().attr(\"").append(attrName).append("\", ").append(paramName).append(");\n");
sb.append("}\n");
sb.append("return self();\n");
}else{
//add a variant where you can toggle the attribute
sb.append("(final boolean enable) {");
sb.append("if (enable){\n");
addAttributeNoArg(sb, attrName);
sb.append("}\n");
sb.append("return self();\n");
}
sb.append("}\n");
}
private static void writeAttributeMethod(String interfaceNameSimple, AttrD attrD, StringBuilder sb, String attrName, String paramName) {
sb.append(makeReturnTypeAndMethodName(
((attrD.hasArgument)?"with":"is")+interfaceNameSimple)
);
if(attrD.hasArgument){
//add a variant where you can specify the argument
sb.append("(final String ").append(paramName).append(") {")
.append("return self().attr(\"").append(attrName).append("\", ").append(paramName).append(");\n");
}else{
//add a variant where you can toggle the attribute
sb.append("() {");
addAttributeNoArg(sb, attrName);
sb.append("return self();\n");
}
sb.append("}\n");
}
public static String interfaceNameFromAttribute(String attribute){
String res = attribute.substring(0,1).toUpperCase()+attribute.substring(1);
return "I" + res;
}
private static Path makePath(String tagLowerCase, final Path absPath){
final String filename = interfaceNameFromAttribute(tagLowerCase)+".java";
return Paths.get(absPath.toString(),relPath,filename);
}
}

View File

@@ -0,0 +1,166 @@
package j2html_codegen.generators;
import j2html_codegen.GeneratorUtil;
import j2html_codegen.model.AttributesList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static j2html_codegen.generators.TagCreatorCodeGenerator.containerTags;
import static j2html_codegen.generators.TagCreatorCodeGenerator.emptyTags;
public final class SpecializedTagClassCodeGenerator {
private static final String relPath = "tags/specialized";
public static void generate(final Path absPath, final boolean delete) throws IOException {
//delete all files in the directory for fresh generation
final Path dir = Paths.get(absPath.toString(),relPath);
GeneratorUtil.deleteAllFilesInDir(dir);
//the delete argument serves to give the possibility
//to delete the classes that were written before
System.out.println("// EmptyTags, generated in " + SpecializedTagClassCodeGenerator.class);
for (final String tag : emptyTags()) {
final String className = classNameFromTag(tag);
final Path path = makePath(absPath,tag);
final List<String> interfaceNames = getInterfaceNamesForTag(tag);
final String classString =
getClassTemplate(
className,
Optional.of("EmptyTag<"+className+">"),
Arrays.asList(
"j2html.tags.EmptyTag",
"j2html.tags.attributes.*"
),
tag,
interfaceNames
);
/*
public InputTag() {
super("input");
}
*/
if(!delete){
System.out.println("writing to "+path);
Files.write(path, classString.getBytes());
}
}
System.out.println("// ContainerTags, generated in " + SpecializedTagClassCodeGenerator.class);
for (final String tag : containerTags()) {
final Path path = makePath(absPath, tag);
final String className = classNameFromTag(tag);
final List<String> interfaceNames = getInterfaceNamesForTag(tag);
final String classString =
getClassTemplate(
className,
Optional.of("ContainerTag<"+className+">"),
Arrays.asList(
"j2html.tags.ContainerTag",
"j2html.tags.attributes.*"
),
tag,
interfaceNames
);
if(delete){
if(Files.exists(path)) {
System.out.println("deleting " + path);
Files.delete(path);
}
}else {
System.out.println("writing to "+path);
Files.write(path, classString.getBytes());
}
}
}
public static String classNameFromTag(String tageNameLowerCase){
String res = tageNameLowerCase.substring(0,1).toUpperCase()+tageNameLowerCase.substring(1);
return res + "Tag";
}
private static Path makePath(final Path absPath, String tagLowerCase){
final String filename = classNameFromTag(tagLowerCase)+".java";
return Paths.get(absPath.toString(),relPath,filename);
}
private static String getPackage(){
return "package j2html.tags.specialized;\n";
}
private static String getClassTemplate(
final String className,
final Optional<String> optExtends,
final List<String> imports,
final String tag,
final List<String> interfaces
){
final StringBuilder sb = new StringBuilder();
sb.append(getPackage());
sb.append("\n");
for(String importName : imports){
sb.append("import ").append(importName).append(";\n");
}
sb.append("\n");
sb.append("public final class ")
.append(className)
.append(" ");
optExtends.ifPresent(ext -> sb.append("extends ").append(ext).append(" "));
//add the 'implements' clause
if(!interfaces.isEmpty()) {
sb.append("\n");
sb.append("implements ");
final List<String> genericInterfaceNames
= interfaces.stream().map(iName -> iName+"<"+className+">")
.collect(Collectors.toList());
sb.append(
String.join(",", genericInterfaceNames)
);
}
sb.append(" {\n");
//class contents
sb.append("public ")
.append(className)
.append("() {")
.append("super(\"").append(tag).append("\");")
.append("}\n");
sb.append("}\n");
return sb.toString();
}
private static List<String> getInterfaceNamesForTag(final String tagNameLowercase){
return AttributesList.getCustomAttributesForHtmlTag(tagNameLowercase)
.stream()
.map(
AttributeInterfaceCodeGenerator::interfaceNameFromAttribute
).collect(Collectors.toList());
}
}

View File

@@ -1,38 +1,56 @@
package j2html.tags;
package j2html_codegen.generators;
import java.util.Arrays;
import java.util.List;
class TagCreatorCodeGenerator {
public final class TagCreatorCodeGenerator {
public static void print() {
public static void main(String[] args) {
System.out.println("// EmptyTags, generated in " + TagCreatorCodeGenerator.class);
for (String tag : emptyTags()) {
String emptyA1 = "public static EmptyTag " + tag + "()";
String emptyA2 = "{ return new EmptyTag(\"" + tag + "\"); }";
final String className = SpecializedTagClassCodeGenerator.classNameFromTag(tag);
final String publicstaticTypeMethod = "public static "+className+" "+tag+" ";
final String castReturn = " return ("+className+") ";
final String construct = " new "+className+"()";
String emptyA1 = publicstaticTypeMethod + "()";
String emptyA2 = "{ return "+construct+"; }";
// Attr shorthands
String emptyB1 = "public static EmptyTag " + tag + "(Attr.ShortForm shortAttr)";
String emptyB2 = "{ return Attr.addTo(new EmptyTag(\"" + tag + "\"), shortAttr); }";
String emptyB1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr)";
String emptyB2 = "{ "+castReturn+" Attr.addTo("+construct+", shortAttr); }";
// Print
System.out.println(String.format("%-80s%1s", emptyA1, emptyA2));
System.out.println(String.format("%-80s%1s", emptyB1, emptyB2));
System.out.println("");
System.out.println();
}
System.out.println("// ContainerTags, generated in " + TagCreatorCodeGenerator.class);
for (String tag : containerTags()) {
String containerA1 = "public static ContainerTag " + tag + "()";
String containerA2 = "{ return new ContainerTag(\"" + tag + "\"); }";
String containerB1 = "public static ContainerTag " + tag + "(String text)";
String containerB2 = "{ return new ContainerTag(\"" + tag + "\").withText(text); }";
String containerC1 = "public static ContainerTag " + tag + "(DomContent... dc)";
String containerC2 = "{ return new ContainerTag(\"" + tag + "\").with(dc); }";
final String className = SpecializedTagClassCodeGenerator.classNameFromTag(tag);
final String publicstaticTypeMethod = "public static "+className+" "+tag+" ";
final String castReturn = " return ("+className+") ";
final String construct = " new "+className+"()";
String containerA1 = publicstaticTypeMethod+ "()";
String containerA2 = "{ "+castReturn + construct + "; }";
String containerB1 = publicstaticTypeMethod + "(String text)";
String containerB2 = "{ "+castReturn + construct + ".withText(text); }";
String containerC1 = publicstaticTypeMethod + "(DomContent... dc)";
String containerC2 = "{ "+castReturn + construct+".with(dc); }";
// Attr shorthands
String containerD1 = "public static ContainerTag " + tag + "(Attr.ShortForm shortAttr)";
String containerD2 = "{ return Attr.addTo(new ContainerTag(\"" + tag + "\"), shortAttr); }";
String containerE1 = "public static ContainerTag " + tag + "(Attr.ShortForm shortAttr, String text)";
String containerE2 = "{ return Attr.addTo(new ContainerTag(\"" + tag + "\").withText(text), shortAttr); }";
String containerF1 = "public static ContainerTag " + tag + "(Attr.ShortForm shortAttr, DomContent... dc)";
String containerF2 = "{ return Attr.addTo(new ContainerTag(\"" + tag + "\").with(dc), shortAttr); }";
String containerD1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr)";
String containerD2 = "{ "+castReturn+" Attr.addTo("+construct+", shortAttr); }";
String containerE1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr, String text)";
String containerE2 = "{ "+castReturn+" Attr.addTo("+construct+".withText(text), shortAttr); }";
String containerF1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr, DomContent... dc)";
String containerF2 = "{ "+castReturn+" Attr.addTo("+construct+".with(dc), shortAttr); }";
// Print
System.out.println(String.format("%-80s%1s", containerA1, containerA2));
System.out.println(String.format("%-80s%1s", containerB1, containerB2));
@@ -40,12 +58,12 @@ class TagCreatorCodeGenerator {
System.out.println(String.format("%-80s%1s", containerD1, containerD2));
System.out.println(String.format("%-80s%1s", containerE1, containerE2));
System.out.println(String.format("%-80s%1s", containerF1, containerF2));
System.out.println("");
System.out.println();
}
}
// This is a method that contains all ContainerTags, there is nothing below it
private static List<String> emptyTags() {
public static List<String> emptyTags() {
return Arrays.asList(
"area",
"base",
@@ -66,7 +84,7 @@ class TagCreatorCodeGenerator {
);
}
private static List<String> containerTags() {
public static List<String> containerTags() {
return Arrays.asList(
"a",
"abbr",
@@ -85,6 +103,7 @@ class TagCreatorCodeGenerator {
"cite",
"code",
"colgroup",
"data",
"datalist",
"dd",
"del",
@@ -130,6 +149,7 @@ class TagCreatorCodeGenerator {
"option",
"output",
"p",
"picture",
"pre",
"progress",
"q",
@@ -141,6 +161,7 @@ class TagCreatorCodeGenerator {
"script",
"section",
"select",
"slot",
"small",
"span",
"strong",
@@ -151,6 +172,7 @@ class TagCreatorCodeGenerator {
"table",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",

View File

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

View File

@@ -0,0 +1,24 @@
package j2html_codegen.model;
public final class AttrD {
//attribute descriptor
public final String attr;
public final boolean hasArgument;
//the html tags that this attribute can be used on
public final String[] tags;
public AttrD(final String attr, boolean hasArgument){
this.attr = attr;
this.hasArgument = hasArgument;
this.tags = new String[]{};
}
public AttrD(final String attr, boolean hasArgument, final String... tags) {
this.attr = attr;
this.hasArgument = hasArgument;
this.tags = tags;
}
}

View File

@@ -0,0 +1,182 @@
package j2html_codegen.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public final class AttributesList {
//https://www.w3schools.com/tags/ref_attributes.asp
public static List<String> getCustomAttributesForHtmlTag(final String tagLowercase){
final List<String> attrs = new ArrayList<>();
for(AttrD attrD : attributesDescriptive()){
if(
Arrays.asList(attrD.tags).contains(tagLowercase)
){
attrs.add(attrD.attr);
}
}
return attrs;
}
public static List<AttrD> attributesDescriptive() {
return Arrays.asList(
new AttrD("accept", true, "input"),
//new AttrD("accept-charset","form"), //contains dashes, TODO
//new AttrD("accesskey"), //global attribute
new AttrD("action", true, "form"),
//"align", not supported in HTML5
new AttrD("alt", true, "area","img","input"),
new AttrD("async", false, "script"),
new AttrD("autocomplete", false, "form","input"),
new AttrD("autofocus", false, "button","input","select","textarea"),
new AttrD("autoplay", false, "audio","video"),
//"bgcolor", not supported in HTMTL5
//"border", not supported in HTML5
new AttrD("charset", true, "meta","script"),
new AttrD("checked", false, "input"),
new AttrD("cite", true, "blockquote","del","ins","q"),
//"class" already implemented in Tag.java // global attribute
new AttrD("cols", true, "textarea"),
new AttrD("colspan", true, "td","th"),
new AttrD("content", true, "meta"),
//"contenteditable" global attribute, should be in Tag.java
new AttrD("controls", false, "audio","video"),
new AttrD("coords", true, "area"),
new AttrD("data", true, "object"),
new AttrD("datetime", true, "del","ins","time"),
new AttrD("default", false, "track"),
new AttrD("defer", false, "script"),
//new AttrD("dir"), //global attribute
new AttrD("dirname", true, "input","textarea"),
new AttrD("disabled",false, "button","fieldset","input","optgroup","option","select","textarea"),
new AttrD("download",false, "a","area"),
//new AttrD("draggable") global attribute, should be in Tag.java
new AttrD("enctype", true, "form"),
new AttrD("for", true, "label","output"),
new AttrD("form", true, "button","fieldset","input","label","meter","object","output","select","textarea"),
new AttrD("formaction", true, "button","input"),
new AttrD("headers", true, "td","th"),
new AttrD("height", true, "canvas","embed","iframe","img","input","object","video"),
//new AttrD("hidden"), global attribute
new AttrD("high", true, "meter"),
new AttrD("href", true, "a","area","base","link"),
new AttrD("hreflang", true, "a","area","link"),
//"http-equiv", //TODO: '-' is problematic in code generation
//"id" global attribute, should be in Tag.java
new AttrD("ismap", false, "img"),
new AttrD("kind", true, "track"),
new AttrD("label", true, "track","option","optgroup"),
//"lang" global attribute, should be in Tag.java
new AttrD("list", true, "input"),
new AttrD("loop", false, "audio","video"),
new AttrD("low", true, "meter"),
new AttrD("max", true, "input","meter","progress"),
new AttrD("maxlength", true, "input","textarea"),
new AttrD("media", true, "a","area","link","source","style"),
new AttrD("method", true, "form"),
new AttrD("min", true, "input","meter"),
new AttrD("multiple", false, "input","select"),
new AttrD("muted", false, "video","audio"),
new AttrD("name", true, "button","fieldset","form","iframe","input","map","meta","object","output","param","select","slot","textarea"),
new AttrD("novalidate", false, "form"),
new AttrD("onabort", true, "audio","embed","img","object","video"),
new AttrD("onafterprint", true, "body"),
new AttrD("onbeforeprint", true, "body"),
new AttrD("onbeforeunload", true, "body"),
//new AttrD("onblur"), global attribute
new AttrD("oncanplay", true, "audio","embed","object","video"),
new AttrD("oncanplaythrough", true, "audio","video"),
/* a bunch of event attributes that are on all visible elements (so should be in Tag.java)
"onchange",
"onclick",
"oncontextmenu",
"oncopy",
*/
new AttrD("oncuechange", true, "track"),
/*
"oncut",
...
"ondrop",
*/
new AttrD("ondurationchange", true, "audio","video"),
new AttrD("onemptied", true, "audio","video"),
new AttrD("onended", true, "audio","video"),
new AttrD("onerror", true, "audio","body","embed","img","object","script","style","video"),
//new AttrD("onfocus"),// global attribute
new AttrD("onhashchange", true, "body"),
// ... a bunch of event attributes visible on all elements
new AttrD("onload", true, "body","iframe","img","input","link","script","style"),
new AttrD("onloadeddata", true, "audio","video"),
new AttrD("onloadedmetadata", true, "audio","video"),
new AttrD("onloadstart", true, "audio","video"),
// ... a bunch of event attributes visible on all elements
new AttrD("onoffline", true, "body"),
new AttrD("ononline", true, "body"),
new AttrD("onpagehide", true, "body"),
new AttrD("onpageshow", true, "body"),
//new AttrD("onpaste"),// global attribute
new AttrD("onpause", true, "audio","video"),
new AttrD("onplay", true, "audio","video"),
new AttrD("onplaying", true, "audio","video"),
new AttrD("onpopstate", true, "body"),
new AttrD("onprogress", true, "audio","video"),
new AttrD("onratechange", true, "audio","video"),
new AttrD("onreset", true, "form"),
new AttrD("onresize", true, "body"),
//new AttrD("onscroll"), //global attribute
new AttrD("onsearch", true, "input"),
new AttrD("onseeked", true, "audio","video"),
new AttrD("onseeking", true, "audio","video"),
//new AttrD("onselect"), //global attribute
new AttrD("onstalled", true, "audio","video"),
new AttrD("onstorage", true, "body"),
new AttrD("onsubmit", true, "form"),
new AttrD("onsuspend", true, "audio","video"),
new AttrD("ontimeupdate", true, "audio","video"),
new AttrD("ontoggle", true, "details"),
new AttrD("onunload", true, "body"),
new AttrD("onvolumechanged", true, "audio","video"),
new AttrD("onwaiting", true, "audio","video"),
//new AttrD("onwheel"), //global attribute
new AttrD("open", false, "details"),
new AttrD("optimum", true, "meter"),
new AttrD("pattern", true, "input"),
new AttrD("placeholder", true, "input","textarea"),
new AttrD("poster", true, "video"),
new AttrD("preload", true, "audio","video"),
new AttrD("readonly", false, "input","textarea"),
new AttrD("rel", true, "a","area","form","link"),
new AttrD("required", false, "input","select","textarea"),
new AttrD("reversed", false, "ol"),
new AttrD("rows", true, "textarea"),
new AttrD("rowspan", true, "td","th"),
new AttrD("sandbox", false, "iframe"),
new AttrD("scope", true, "th"),
new AttrD("selected", false, "option"),
new AttrD("shape", true, "area"),
new AttrD("size", true, "input","select"),
new AttrD("sizes", true, "img","link","source"),
new AttrD("span", true, "col","colgroup"),
//new AttrD("spellcheck"), //global attribute
new AttrD("src", true, "audio","embed","iframe","img","input","script","source","track","video"),
new AttrD("srcdoc", true, "iframe"),
new AttrD("srclang", true, "track"),
new AttrD("srcset", true, "img","source"),
new AttrD("start", true, "ol"),
new AttrD("step", true, "input"),
//new AttrD("style"), //global attribute
//new AttrD("tabindex"), //global attribute
new AttrD("target", true, "a","area","base","form"),
//new AttrD("title"), //global attribute
//new AttrD("translate"),// global attribute
new AttrD("type", true, "a","button","embed","input","link","menu","object","script","source","style"),
new AttrD("usemap", true, "img","object"),
new AttrD("value", true, "button","data","input","li","option","meter","progress","param"),
new AttrD("width", true, "canvas","embed","iframe","img","input","object","video"),
new AttrD("wrap", true, "textarea")
);
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
package j2html_codegen;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class AppTest
{
@Test
public void shouldAnswerWithTrue()
{
//dummy, just to conform to the default mvn
//directory layout
assertTrue( true );
}
}

View File

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

File diff suppressed because it is too large Load Diff

19
docs/404.html Normal file

File diff suppressed because one or more lines are too long

1
docs/CNAME Normal file
View File

@@ -0,0 +1 @@
j2html.com

3
docs/README Normal file
View File

@@ -0,0 +1,3 @@
# j2html.com
Don't edit these files manually, they are generated from `j2html/website`.

121
docs/css/prism.css Normal file
View File

@@ -0,0 +1,121 @@
/* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+java */
/**
* okaidia theme for JavaScript, CSS and HTML
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
* @author ocodia
*/
code[class*="language-"],
pre[class*="language-"] {
color: #f8f8f2;
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
font-family: Consolas, Monaco, 'Andale Mono', monospace;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #272822;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #f8f8f2;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted {
color: #f92672;
}
.token.boolean,
.token.number {
color: #ae81ff;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #a6e22e;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: #f8f8f2;
}
.token.atrule,
.token.attr-value,
.token.function {
color: #e6db74;
}
.token.keyword {
color: #66d9ef;
}
.token.regex,
.token.important {
color: #fd971f;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

24
docs/download.html Normal file

File diff suppressed because one or more lines are too long

5
docs/download_html.bat Normal file
View File

@@ -0,0 +1,5 @@
curl "http://localhost:8888/" > index.html
curl "http://localhost:8888/download.html" > download.html
curl "http://localhost:8888/examples.html" > examples.html
curl "http://localhost:8888/news.html" > news.html
curl "http://localhost:8888/404.html" > 404.html

426
docs/examples.html Normal file

File diff suppressed because one or more lines are too long

32
docs/img/favicon.svg Normal file
View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<desc>Created with Lunacy</desc>
<defs>
<path d="M0 0L512 0L512 512L0 512L0 0Z" id="path_1" />
<rect width="512" height="512" id="artboard_1" />
<clipPath id="clip_1">
<use xlink:href="#artboard_1" clip-rule="evenodd" />
</clipPath>
<clipPath id="mask_1">
<use xlink:href="#path_1" />
</clipPath>
</defs>
<g id="Artboard-Copy-3" clip-path="url(#clip_1)">
<g id="HTML5_Badge">
<path d="M0 0L512 0L512 512L0 512L0 0Z" id="Background" fill="none" stroke="none" />
<g clip-path="url(#mask_1)">
<path d="M41 460L0 0L451 0L410 460L225 512" transform="translate(30 0)" id="Shape" fill="#1272BF" stroke="none" />
<path d="M0 435L149 394L184 0L0 0" transform="translate(256 37)" id="Shape" fill="#FFFFFF" fill-opacity="0.21176471" stroke="none" />
</g>
</g>
<g id="coffee-icon-12" transform="translate(122.26813 68.45154)">
<g id="Group">
<path d="M109.76 63.5233C72.71 61.7926 37.5831 55.1903 18.2249 46.4086C5.14845 40.4472 -1.64617 32.627 0.340938 25.8965C3.22545 16.2815 18.7377 7.94844 45.8521 1.41021L51.7493 0L56.2363 4.23062C58.7362 6.53823 61.8771 9.16634 63.2232 10.1278C64.6975 11.0893 65.9154 12.4355 66.1718 13.4611C66.3641 14.4226 67.582 16.7302 68.864 18.5891C74.1202 26.2811 84.5686 31.1527 101.812 33.8449C112.452 35.5756 153.028 35.5756 164.117 33.909C181.424 31.2809 192.193 26.3452 197.449 18.5891C198.731 16.7302 199.949 14.4226 200.141 13.4611C200.398 12.4996 201.551 11.0893 202.897 10.1919C204.244 9.35865 207.384 6.66643 209.884 4.35882L214.564 1.95618e-05L220.461 1.41023C251.742 8.78175 269.177 19.9993 265.972 30.7681C264.818 34.55 261.677 38.2678 256.87 41.4728C233.025 57.3056 171.937 66.4719 109.76 63.5233L109.76 63.5233Z" transform="translate(0 276.47668)" id="Shape" fill="#FFFFFF" stroke="none" />
<path d="M83.9789 173.473C64.877 172.256 51.8006 168.217 47.9546 162.32C47.3777 161.423 46.7367 159.435 46.6084 157.897C46.2879 155.397 45.9033 154.82 43.2752 152.961C38.5959 149.564 29.8142 140.334 25.0708 133.731C20.0069 126.68 12.1225 111.04 9.30215 102.45C3.14853 83.9255 0.199922 64.5031 0.00761933 41.8116C-0.0564799 29.1198 0.199922 27.8378 4.11003 22.838C6.73814 19.633 14.1738 14.6332 20.5197 12.0051C54.3005 -2.09699 127.952 -4.14819 171.732 7.83854C187.565 12.1974 198.334 18.6074 202.436 26.1712C204.038 29.0557 204.359 30.4659 204.615 35.8503L204.622 35.9837C204.936 41.9426 204.946 42.1293 206.282 41.7475C206.987 41.4911 210.833 41.2988 214.679 41.2988C225.384 41.3629 232.371 43.991 238.012 50.1447C250.96 64.2467 246.729 91.297 228.525 110.271C217.371 121.873 203.333 129.116 186.924 131.744L180.193 132.834L177.95 135.718C172.822 142.128 167.373 147.769 162.886 151.423C158.591 154.884 158.079 155.525 157.822 157.961C157.374 161.358 156.092 163.41 152.694 165.846C143.72 172.32 115.452 175.461 83.9789 173.473ZM179.103 35.2734C179.103 41.1706 159.104 46.9396 130.708 49.119C121.285 49.8241 97.4399 50.401 89.6838 50.0805C57.2491 48.6703 33.0833 44.1192 26.7374 38.1579C23.5965 35.2093 25.4554 31.9402 32.0577 28.9274C40.2625 25.1455 55.0056 22.0687 74.2997 20.2098C85.1967 19.1842 119.362 19.1842 130.067 20.2739C160.322 23.2225 179.103 28.9915 179.103 35.2734ZM48.5956 75.2078C49.9416 100.591 56.0312 124.565 65.3898 141.295C66.6077 143.539 67.5051 145.333 67.3128 145.333C66.159 145.333 50.9673 133.09 47.6981 129.501C36.737 117.514 28.8527 101.168 24.3657 81.2332C22.9555 74.695 20.5838 59.4391 20.9684 58.9904C21.0966 58.9263 23.8529 59.8878 27.1861 61.1698C30.5193 62.3877 36.4165 64.1185 40.3266 65.0159C44.1726 65.9133 47.5058 66.6824 47.7622 66.8107C47.9545 67.003 48.3391 70.7208 48.5956 75.2078ZM201.731 105.399C221.41 95.2071 231.089 77.4513 222.884 66.5543C220.256 63.0929 216.474 61.8109 210.064 62.1314C207.115 62.2596 204.423 62.7083 204.039 63.0929C203.654 63.4775 202.757 67.5158 202.116 72.0669C200.577 82.7075 198.205 92.9636 195.129 102.13C194.955 102.641 194.789 103.12 194.634 103.57C193.539 106.749 192.961 108.429 193.474 108.889C194.05 109.405 196 108.388 200.133 106.231C200.633 105.97 201.165 105.693 201.731 105.399Z" transform="translate(30.973198 125.50244)" id="Shape" fill="#FFFFFF" fill-rule="evenodd" stroke="none" />
<path d="M1.14148 89.3819C4.64379 86.2444 7.12459 81.9395 8.43796 76.759C9.75133 71.5055 9.6054 66.5439 7.63534 50.4917C4.93565 29.113 10.335 17.4386 27.7007 6.93166C32.2975 4.23196 34.0486 3.28342 39.375 1.09447L41.9288 0L38.6454 2.77266C32.8812 7.66131 29.8896 11.3095 27.3358 16.563C23.5417 24.2243 22.885 30.2074 24.3443 43.633C25.7306 56.7666 25.8036 61.2175 24.4902 66.6169C22.4472 75.2997 15.5885 82.961 5.00862 88.3604C-0.536715 91.1331 -0.901539 91.206 1.14148 89.3819L1.14148 89.3819Z" transform="matrix(0.9848077 -0.17364818 0.17364818 0.9848077 109.50348 7.280884)" id="Shape" fill="#FFFFFF" stroke="none" />
<path d="M2.2619 64.5395C7.44241 59.1401 8.53688 53.3759 6.63979 41.6286C4.15899 25.8682 5.54532 19.1554 12.9148 12.0048C16.0523 8.86735 19.1168 6.82434 24.6621 4.1976C34.2935 -0.472146 36.8472 -1.20179 32.6882 1.78976C27.9455 5.29208 24.5891 8.94032 22.4002 13.0993C20.5031 16.7476 20.4301 17.1853 20.722 26.3789C21.3057 46.7361 21.2328 49.5817 19.8464 52.1355C16.8549 57.6808 9.77728 63.2991 2.18894 66.1447L0 66.9473L2.26191 64.5395L2.2619 64.5395Z" transform="matrix(0.9848077 -0.17364818 0.17364818 0.9848077 67.97293 38.71814)" id="Shape" fill="#FFFFFF" stroke="none" />
<path d="M2.43573 63.7984C7.25141 58.326 7.98106 53.5833 5.86508 40.8875C3.01946 23.5948 6.30288 14.7661 18.1961 7.32363C22.72 4.55097 33.2269 -0.410636 33.7376 0.0271519C33.8836 0.173082 32.2054 1.70534 30.0164 3.38353C25.2737 7.10474 22.2092 11.3367 20.458 16.5902C19.5095 19.6547 19.3635 21.7707 19.7284 27.0971C21.1877 47.3813 21.1877 47.4543 19.4365 51.4674C17.3935 56.356 12.3589 61.0257 5.64618 64.3821C2.80055 65.8414 0.319755 67.0089 0.0278784 67.0089C-0.191007 67.0089 0.903465 65.5496 2.43571 63.7984L2.43573 63.7984Z" transform="matrix(0.9848077 -0.17364818 0.17364818 0.9848077 160.54498 22.258545)" id="Shape" fill="#FFFFFF" stroke="none" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

33
docs/img/logo.svg Normal file
View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="1720px" height="512px" viewBox="0 0 1720 512" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<desc>Created with Lunacy</desc>
<defs>
<path d="M0 0L512 0L512 512L0 512L0 0Z" id="path_1" />
<rect width="1720" height="512" id="artboard_1" />
<clipPath id="clip_1">
<use xlink:href="#artboard_1" clip-rule="evenodd" />
</clipPath>
<clipPath id="mask_1">
<use xlink:href="#path_1" />
</clipPath>
</defs>
<g id="Artboard-Copy-2" clip-path="url(#clip_1)">
<g id="HTML5_Badge">
<path d="M0 0L512 0L512 512L0 512L0 0Z" id="Background" fill="none" stroke="none" />
<g clip-path="url(#mask_1)">
<path d="M41 460L0 0L451 0L410 460L225 512" transform="translate(30 0)" id="Shape" fill="#1272BF" stroke="none" />
<path d="M0 435L149 394L184 0L0 0" transform="translate(256 37)" id="Shape" fill="#FFFFFF" fill-opacity="0.21176471" stroke="none" />
</g>
</g>
<g id="coffee-icon-12" transform="translate(122.26813 68.45154)">
<g id="Group">
<path d="M109.76 63.5233C72.71 61.7926 37.5831 55.1903 18.2249 46.4086C5.14845 40.4472 -1.64617 32.627 0.340938 25.8965C3.22545 16.2815 18.7377 7.94844 45.8521 1.41021L51.7493 0L56.2363 4.23062C58.7362 6.53823 61.8771 9.16634 63.2232 10.1278C64.6975 11.0893 65.9154 12.4355 66.1718 13.4611C66.3641 14.4226 67.582 16.7302 68.864 18.5891C74.1202 26.2811 84.5686 31.1527 101.812 33.8449C112.452 35.5756 153.028 35.5756 164.117 33.909C181.424 31.2809 192.193 26.3452 197.449 18.5891C198.731 16.7302 199.949 14.4226 200.141 13.4611C200.398 12.4996 201.551 11.0893 202.897 10.1919C204.244 9.35865 207.384 6.66643 209.884 4.35882L214.564 1.95618e-05L220.461 1.41023C251.742 8.78175 269.177 19.9993 265.972 30.7681C264.818 34.55 261.677 38.2678 256.87 41.4728C233.025 57.3056 171.937 66.4719 109.76 63.5233L109.76 63.5233Z" transform="translate(0 276.47668)" id="Shape" fill="#FFFFFF" stroke="none" />
<path d="M83.9789 173.473C64.877 172.256 51.8006 168.217 47.9546 162.32C47.3777 161.423 46.7367 159.435 46.6084 157.897C46.2879 155.397 45.9033 154.82 43.2752 152.961C38.5959 149.564 29.8142 140.334 25.0708 133.731C20.0069 126.68 12.1225 111.04 9.30215 102.45C3.14853 83.9255 0.199922 64.5031 0.00761933 41.8116C-0.0564799 29.1198 0.199922 27.8378 4.11003 22.838C6.73814 19.633 14.1738 14.6332 20.5197 12.0051C54.3005 -2.09699 127.952 -4.14819 171.732 7.83854C187.565 12.1974 198.334 18.6074 202.436 26.1712C204.038 29.0557 204.359 30.4659 204.615 35.8503L204.622 35.9837C204.936 41.9426 204.946 42.1293 206.282 41.7475C206.987 41.4911 210.833 41.2988 214.679 41.2988C225.384 41.3629 232.371 43.991 238.012 50.1447C250.96 64.2467 246.729 91.297 228.525 110.271C217.371 121.873 203.333 129.116 186.924 131.744L180.193 132.834L177.95 135.718C172.822 142.128 167.373 147.769 162.886 151.423C158.591 154.884 158.079 155.525 157.822 157.961C157.374 161.358 156.092 163.41 152.694 165.846C143.72 172.32 115.452 175.461 83.9789 173.473ZM179.103 35.2734C179.103 41.1706 159.104 46.9396 130.708 49.119C121.285 49.8241 97.4399 50.401 89.6838 50.0805C57.2491 48.6703 33.0833 44.1192 26.7374 38.1579C23.5965 35.2093 25.4554 31.9402 32.0577 28.9274C40.2625 25.1455 55.0056 22.0687 74.2997 20.2098C85.1967 19.1842 119.362 19.1842 130.067 20.2739C160.322 23.2225 179.103 28.9915 179.103 35.2734ZM48.5956 75.2078C49.9416 100.591 56.0312 124.565 65.3898 141.295C66.6077 143.539 67.5051 145.333 67.3128 145.333C66.159 145.333 50.9673 133.09 47.6981 129.501C36.737 117.514 28.8527 101.168 24.3657 81.2332C22.9555 74.695 20.5838 59.4391 20.9684 58.9904C21.0966 58.9263 23.8529 59.8878 27.1861 61.1698C30.5193 62.3877 36.4165 64.1185 40.3266 65.0159C44.1726 65.9133 47.5058 66.6824 47.7622 66.8107C47.9545 67.003 48.3391 70.7208 48.5956 75.2078ZM201.731 105.399C221.41 95.2071 231.089 77.4513 222.884 66.5543C220.256 63.0929 216.474 61.8109 210.064 62.1314C207.115 62.2596 204.423 62.7083 204.039 63.0929C203.654 63.4775 202.757 67.5158 202.116 72.0669C200.577 82.7075 198.205 92.9636 195.129 102.13C194.955 102.641 194.789 103.12 194.634 103.57C193.539 106.749 192.961 108.429 193.474 108.889C194.05 109.405 196 108.388 200.133 106.231C200.633 105.97 201.165 105.693 201.731 105.399Z" transform="translate(30.973198 125.50244)" id="Shape" fill="#FFFFFF" fill-rule="evenodd" stroke="none" />
<path d="M1.14148 89.3819C4.64379 86.2444 7.12459 81.9395 8.43796 76.759C9.75133 71.5055 9.6054 66.5439 7.63534 50.4917C4.93565 29.113 10.335 17.4386 27.7007 6.93166C32.2975 4.23196 34.0486 3.28342 39.375 1.09447L41.9288 0L38.6454 2.77266C32.8812 7.66131 29.8896 11.3095 27.3358 16.563C23.5417 24.2243 22.885 30.2074 24.3443 43.633C25.7306 56.7666 25.8036 61.2175 24.4902 66.6169C22.4472 75.2997 15.5885 82.961 5.00862 88.3604C-0.536715 91.1331 -0.901539 91.206 1.14148 89.3819L1.14148 89.3819Z" transform="matrix(0.9848077 -0.17364818 0.17364818 0.9848077 109.50348 7.280884)" id="Shape" fill="#FFFFFF" stroke="none" />
<path d="M2.2619 64.5395C7.44241 59.1401 8.53688 53.3759 6.63979 41.6286C4.15899 25.8682 5.54532 19.1554 12.9148 12.0048C16.0523 8.86735 19.1168 6.82434 24.6621 4.1976C34.2935 -0.472146 36.8472 -1.20179 32.6882 1.78976C27.9455 5.29208 24.5891 8.94032 22.4002 13.0993C20.5031 16.7476 20.4301 17.1853 20.722 26.3789C21.3057 46.7361 21.2328 49.5817 19.8464 52.1355C16.8549 57.6808 9.77728 63.2991 2.18894 66.1447L0 66.9473L2.26191 64.5395L2.2619 64.5395Z" transform="matrix(0.9848077 -0.17364818 0.17364818 0.9848077 67.97293 38.71814)" id="Shape" fill="#FFFFFF" stroke="none" />
<path d="M2.43573 63.7984C7.25141 58.326 7.98106 53.5833 5.86508 40.8875C3.01946 23.5948 6.30288 14.7661 18.1961 7.32363C22.72 4.55097 33.2269 -0.410636 33.7376 0.0271519C33.8836 0.173082 32.2054 1.70534 30.0164 3.38353C25.2737 7.10474 22.2092 11.3367 20.458 16.5902C19.5095 19.6547 19.3635 21.7707 19.7284 27.0971C21.1877 47.3813 21.1877 47.4543 19.4365 51.4674C17.3935 56.356 12.3589 61.0257 5.64618 64.3821C2.80055 65.8414 0.319755 67.0089 0.0278784 67.0089C-0.191007 67.0089 0.903465 65.5496 2.43571 63.7984L2.43573 63.7984Z" transform="matrix(0.9848077 -0.17364818 0.17364818 0.9848077 160.54498 22.258545)" id="Shape" fill="#FFFFFF" stroke="none" />
</g>
</g>
<path d="M364 10.8L376.4 0L438 0L422.8 125.2C428.667 115.067 436.267 106.867 445.6 100.6C454.933 94.3334 465.867 91.2 478.4 91.2Q502.8 91.2 519 106.4C529.8 116.533 533.867 132.267 531.2 153.6L521.2 236L544.4 236L540.8 265.2L528.8 275.2L467.2 275.2L479.6 173.6C481.733 158.133 480.667 147.067 476.4 140.4C472.133 133.733 465.067 130.4 455.2 130.4C443.733 130.4 435.067 134 429.2 141.2Q420.4 152 417.2 169.6L404.4 275.2L354.8 275.2L383.6 39.2L360.4 39.2L364 10.8ZM1052 0L1039.6 10.8L1036 39.2L1060.8 39.2L1032 275.2L1094 275.2L1106 265.2L1109.6 236L1086.4 236L1115.2 0L1052 0ZM79.2 62.8L82.0851 35.1857Q82.1542 34.5246 82.2534 33.8673Q82.3527 33.2101 82.482 32.5581Q82.6113 31.9061 82.7704 31.2607Q82.9294 30.6154 83.1178 29.9779Q83.3063 29.3405 83.5237 28.7124Q83.7411 28.0843 83.9872 27.4668Q84.2331 26.8493 84.5072 26.2437Q84.7812 25.6381 85.0827 25.0458Q85.3842 24.4534 85.7125 23.8754Q86.0408 23.2975 86.3953 22.7352Q86.7497 22.1729 87.1295 21.6274Q87.5094 21.0819 87.9138 20.5545Q88.3183 20.027 88.7464 19.5186Q89.1746 19.0101 89.6256 18.5219Q90.0767 18.0336 90.5496 17.5666Q91.0225 17.0995 91.5163 16.6546Q92.0101 16.2097 92.5238 15.7878Q93.0375 15.366 93.57 14.9682Q94.1025 14.5703 94.6526 14.1973Q95.2028 13.8243 95.7694 13.4768Q96.3361 13.1294 96.9181 12.8083Q97.5 12.4872 98.0961 12.1931Q98.6922 11.899 99.3012 11.6325Q99.9101 11.366 100.531 11.1277Q101.151 10.8894 101.782 10.6798Q102.413 10.4702 103.052 10.2897Q103.692 10.1092 104.339 9.95819Q104.987 9.80719 105.64 9.68601Q106.294 9.56483 106.952 9.47372Q107.611 9.38261 108.273 9.32177Q108.934 9.26092 109.598 9.23047Q110.262 9.20001 110.927 9.20001L132.8 9.20001L129.915 36.8144Q129.846 37.4754 129.747 38.1327Q129.647 38.7899 129.518 39.4419Q129.389 40.0939 129.23 40.7393Q129.071 41.3847 128.882 42.0221Q128.694 42.6595 128.476 43.2876Q128.259 43.9157 128.013 44.5332Q127.767 45.1507 127.493 45.7563Q127.219 46.3619 126.917 46.9543Q126.616 47.5466 126.288 48.1246Q125.959 48.7025 125.605 49.2648Q125.25 49.8271 124.87 50.3726Q124.491 50.9181 124.086 51.4456Q123.682 51.9731 123.254 52.4815Q122.825 52.9899 122.374 53.4781Q121.923 53.9664 121.45 54.4334Q120.978 54.9005 120.484 55.3454Q119.99 55.7904 119.476 56.2122Q118.963 56.634 118.43 57.0319Q117.898 57.4297 117.347 57.8027Q116.797 58.1758 116.231 58.5232Q115.664 58.8706 115.082 59.1917Q114.5 59.5128 113.904 59.8069Q113.308 60.101 112.699 60.3675Q112.09 60.634 111.469 60.8723Q110.849 61.1106 110.218 61.3202Q109.587 61.5298 108.948 61.7103Q108.308 61.8908 107.661 62.0418Q107.013 62.1928 106.36 62.314Q105.706 62.4352 105.048 62.5263Q104.389 62.6174 103.727 62.6782Q103.066 62.7391 102.402 62.7695Q101.738 62.8 101.073 62.8L79.2 62.8ZM578.8 94.8L565.2 107.2L561.6 134L585.6 134L574.8 221.2C572.667 239.867 576.867 254.2 587.4 264.2C597.933 274.2 612.8 279.2 632 279.2C649.867 279.2 663.733 274.4 673.6 264.8C683.467 255.2 690.933 240 696 219.2L661.2 207.6Q656 228.4 649.8 234.2C645.667 238.067 641.467 240 637.2 240Q632.752 240 629.894 237.868Q623.241 232.907 625.2 216.4L635.2 134L676.8 134L690.4 121.2L694 94.8L640 94.8L646.8 40.0001L610.8 40.0001L595.2 56L590.4 94.8L578.8 94.8ZM158.4 139.6L198.4 144L207.6 114.4C211.867 112.267 217.2 110.733 223.6 109.8C230 108.867 235.467 108.4 240 108.4C248.8 108.4 256.333 110 262.6 113.2C268.867 116.4 272 122 272 130C272 140.933 265.933 151.733 253.8 162.4Q235.6 178.4 206.2 195.4C186.6 206.733 165.467 219.2 142.8 232.8L143.2 275.2L321.2 275.2L330 204.4L288.8 200.4L284.4 229.6L206.8 229.6C225.2 220.267 243.867 210.667 262.8 200.8C281.733 190.933 297.6 179.4 310.4 166.2Q329.6 146.4 329.6 118.4Q329.6 96 313.2 79.4C302.267 68.3333 282.533 62.8 254 62.8Q240 62.8 218.4 65.8Q196.8 68.8 173.6 78.4L158.4 139.6ZM724 94.8L779.6 94.8L781.2 129.6C787.333 118.133 795.2 108.867 804.8 101.8C814.4 94.7334 826 91.2 839.6 91.2C852.667 91.2 863.6 94.4667 872.4 101C881.2 107.533 886.533 117.6 888.4 131.2Q897.6 113.6 912.2 102.4C921.933 94.9333 933.733 91.2 947.6 91.2C963.867 91.2 976.733 96.2667 986.2 106.4C995.667 116.533 999.067 132.267 996.4 153.6L986.4 236L1009.6 236L1006 265.2L994 275.2L932.4 275.2L944.8 173.6C946.667 158.133 945.733 147.067 942 140.4C938.267 133.733 931.467 130.4 921.6 130.4Q903.6 130.4 896 142.4C890.933 150.4 887.6 160.667 886 173.2L873.6 275.2L824.4 275.2L836.8 173.6C838.667 158.133 837.733 147.067 834 140.4C830.267 133.733 823.467 130.4 813.6 130.4Q795.6 130.4 788 142.4C782.933 150.4 779.6 160.667 778 173.2L765.6 275.2L716 275.2L733.2 134L708 134L711.6 105.6L724 94.8ZM57.2 94.8L44.8 105.6L41.2 134L70.4 134L54 268.8Q48.8 312 35.4 319.2C26.4667 324 15.3333 323.867 2 318.8L0 352Q17.2 364 40 364C58.9333 364 73.3333 354.933 83.2 336.8C93.0667 318.667 99.7333 296 103.2 268.8L124.8 94.8L57.2 94.8Z" transform="translate(592 103.83917)" id="j2html" fill="#222222" fill-rule="evenodd" stroke="none" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

33
docs/index.html Normal file

File diff suppressed because one or more lines are too long

7
docs/js/prism.js Normal file
View File

@@ -0,0 +1,7 @@
/* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+java */
self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{};var Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var a={};for(var r in e)e.hasOwnProperty(r)&&(a[r]=t.util.clone(e[r]));return a;case"Array":return e.map(function(e){return t.util.clone(e)})}return e}},languages:{extend:function(e,n){var a=t.util.clone(t.languages[e]);for(var r in n)a[r]=n[r];return a},insertBefore:function(e,n,a,r){r=r||t.languages;var i=r[e];if(2==arguments.length){a=arguments[1];for(var l in a)a.hasOwnProperty(l)&&(i[l]=a[l]);return i}var s={};for(var o in i)if(i.hasOwnProperty(o)){if(o==n)for(var l in a)a.hasOwnProperty(l)&&(s[l]=a[l]);s[o]=i[o]}return t.languages.DFS(t.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=s)}),r[e]=s},DFS:function(e,n,a){for(var r in e)e.hasOwnProperty(r)&&(n.call(e,r,e[r],a||r),"Object"===t.util.type(e[r])?t.languages.DFS(e[r],n):"Array"===t.util.type(e[r])&&t.languages.DFS(e[r],n,r))}},highlightAll:function(e,n){for(var a,r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'),i=0;a=r[i++];)t.highlightElement(a,e===!0,n)},highlightElement:function(a,r,i){for(var l,s,o=a;o&&!e.test(o.className);)o=o.parentNode;if(o&&(l=(o.className.match(e)||[,""])[1],s=t.languages[l]),s){a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+l,o=a.parentNode,/pre/i.test(o.nodeName)&&(o.className=o.className.replace(e,"").replace(/\s+/g," ")+" language-"+l);var u=a.textContent;if(u){u=u.replace(/^(?:\r?\n|\r)/,"");var g={element:a,language:l,grammar:s,code:u};if(t.hooks.run("before-highlight",g),r&&self.Worker){var c=new Worker(t.filename);c.onmessage=function(e){g.highlightedCode=n.stringify(JSON.parse(e.data),l),t.hooks.run("before-insert",g),g.element.innerHTML=g.highlightedCode,i&&i.call(g.element),t.hooks.run("after-highlight",g)},c.postMessage(JSON.stringify({language:g.language,code:g.code}))}else g.highlightedCode=t.highlight(g.code,g.grammar,g.language),t.hooks.run("before-insert",g),g.element.innerHTML=g.highlightedCode,i&&i.call(a),t.hooks.run("after-highlight",g)}}},highlight:function(e,a,r){var i=t.tokenize(e,a);return n.stringify(t.util.encode(i),r)},tokenize:function(e,n){var a=t.Token,r=[e],i=n.rest;if(i){for(var l in i)n[l]=i[l];delete n.rest}e:for(var l in n)if(n.hasOwnProperty(l)&&n[l]){var s=n[l];s="Array"===t.util.type(s)?s:[s];for(var o=0;o<s.length;++o){var u=s[o],g=u.inside,c=!!u.lookbehind,f=0,h=u.alias;u=u.pattern||u;for(var p=0;p<r.length;p++){var d=r[p];if(r.length>e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),N=[p,1];b&&N.push(b);var O=new a(l,g?t.tokenize(m,g):m,h);N.push(O),w&&N.push(w),Array.prototype.splice.apply(r,N)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var i={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}t.hooks.run("wrap",i);var s="";for(var o in i.attributes)s+=o+'="'+(i.attributes[o]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+s+">"+i.content+"</"+i.tag+">"},!self.document)return self.addEventListener?(self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code;self.postMessage(JSON.stringify(t.util.encode(t.tokenize(r,t.languages[a])))),self.close()},!1),self.Prism):self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism);;
Prism.languages.markup={comment:/<!--[\w\W]*?-->/,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|[^\s'">=]+))?\s*)*\/?>/i,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/=|>|"/}},punctuation:/\/?>/,"attr-name":{pattern:/[\w:-]+/,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(t){"entity"===t.type&&(t.attributes.title=t.content.replace(/&amp;/,"&"))});;
Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{punctuation:/[;:]/}},url:/url\((?:(["'])(\\\n|\\?.)*?\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*(?=\s*\{)/,string:/("|')(\\\n|\\?.)*?\1/,property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,punctuation:/[\{\};:]/,"function":/[-a-z0-9]+(?=\()/i},Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/<style[\w\W]*?>[\w\W]*?<\/style>/i,inside:{tag:{pattern:/<style[\w\W]*?>|<\/style>/i,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.css},alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag));;
Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:/("|')(\\\n|\\?.)*?\1/,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":{pattern:/[a-z0-9_]+\(/i,inside:{punctuation:/\(/}},number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|&{1,2}|\|?\||\?|\*|\/|~|\^|%/,ignore:/&(lt|gt|amp);/i,punctuation:/[{}[\];(),.:]/};;
Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|function|get|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|-?Infinity)\b/,"function":/(?!\d)[a-z0-9_$]+(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/<script[\w\W]*?>[\w\W]*?<\/script>/i,inside:{tag:{pattern:/<script[\w\W]*?>|<\/script>/i,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript},alias:"language-javascript"}});;
Prism.languages.java=Prism.languages.extend("clike",{keyword:/\b(abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while)\b/,number:/\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp\-]+\b|\b\d*\.?\d+[e]?[\d]*[df]\b|\b\d*\.?\d+\b/i,operator:{pattern:/(^|[^\.])(?:\+=|\+\+?|-=|--?|!=?|<{1,2}=?|>{1,3}=?|==?|&=|&&?|\|=|\|\|?|\?|\*=?|\/=?|%=?|\^=?|:|~)/m,lookbehind:!0}});;

19
docs/news.html Normal file

File diff suppressed because one or more lines are too long

101
library/.gitignore vendored Normal file
View File

@@ -0,0 +1,101 @@
# Created by https://www.gitignore.io
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Windows ###
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### Java ###
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
### Eclipse ###
.classpath
.project
.settings/
buildNumber.properties

View File

@@ -10,7 +10,7 @@
<groupId>com.j2html</groupId>
<artifactId>j2html</artifactId>
<version>1.2.3-SNAPSHOT</version>
<version>1.6.1-SNAPSHOT</version>
<name>j2html</name>
<description>Java to HTML builder with a fluent API</description>
@@ -43,7 +43,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -72,12 +72,6 @@
<version>1.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -85,20 +79,39 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.3</version>
<executions>
<execution>
<id>default</id>
<goals>
<goal>perform</goal>
</goals>
<configuration>
<pomFileName>library/pom.xml</pomFileName>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<version>3.10.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<optimize>true</optimize>
<compilerArgs>
<arg>-Xlint:all</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4</version>
<version>3.1.0</version>
<executions>
<execution>
<id>enforce-java</id>
@@ -118,10 +131,78 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
<version>3.4.0</version>
<configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Automatic-Module-Name>com.j2html</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>5.1.6</version>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.revapi</groupId>
<artifactId>revapi-maven-plugin</artifactId>
<version>0.14.6</version>
<dependencies>
<dependency>
<groupId>org.revapi</groupId>
<artifactId>revapi-java</artifactId>
<version>0.26.1</version>
</dependency>
</dependencies>
<configuration>
<oldArtifacts>
<artifact>com.j2html:j2html:1.6.0</artifact>
</oldArtifacts>
<analysisConfiguration>
<revapi.differences>
<differences>
<item>
<ignore>true</ignore>
<code>java.class.removed</code>
<old>class j2html.tags.specialized.GenerateTag</old>
<justification>
This class should never have been used. It was introduced
accidentally by find+replace in the code generator class.
</justification>
</item>
</differences>
</revapi.differences>
</analysisConfiguration>
</configuration>
<executions>
<execution>
<id>check</id>
<goals><goal>check</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
@@ -139,7 +220,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<version>3.0.1</version>
<executions>
<execution>
<id>sign-artifacts</id>

View File

@@ -0,0 +1,148 @@
package j2html;
import j2html.utils.CSSMin;
import j2html.utils.EscapeUtil;
import j2html.utils.Indenter;
import j2html.utils.JSMin;
import j2html.utils.Minifier;
import 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 https://github.com/barryvan/CSSMin
*/
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 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 final Config defaults() {
return DEFAULTS;
}
public static final Config global() {
return new Config(
textEscaper,
cssMinifier,
jsMinifier,
closeEmptyTags,
indenter
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,63 +2,7 @@ package j2html.attributes;
import j2html.tags.Tag;
public class Attr {
public static class ShortForm {
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);
}
}
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, ShortForm shortForm) {
if (shortForm.hasId() && shortForm.hasClasses()) {
return tag.withId(shortForm.id).withClass(shortForm.classes);
}
if (shortForm.hasId()) {
return tag.withId(shortForm.id);
}
if (shortForm.hasClasses()) {
return tag.withClass(shortForm.classes);
}
return tag;
}
private Attr() {
}
public abstract class Attr {
public static final String ACCEPT = "accept";
public static final String ACCEPT_CHARSET = "accept-charset";
@@ -109,6 +53,7 @@ public class Attr {
public static final String HTTP_EQUIV = "http-equiv";
public static final String ICON = "icon";
public static final String ID = "id";
public static final String IS = "is";
public static final String ISMAP = "ismap";
public static final String ITEMPROP = "itemprop";
public static final String KEYTYPE = "keytype";
@@ -152,6 +97,7 @@ public class Attr {
public static final String SHAPE = "shape";
public static final String SIZE = "size";
public static final String SIZES = "sizes";
public static final String SLOT = "slot";
public static final String SPAN = "span";
public static final String SPELLCHECK = "spellcheck";
public static final String SRC = "src";
@@ -170,5 +116,67 @@ public class Attr {
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 j2html.attributes;
import j2html.Config;
import j2html.rendering.TagBuilder;
import 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 j2html.rendering;
import j2html.Config;
import 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 final <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 final <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 final 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 final 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 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 j2html.rendering;
import j2html.Config;
import j2html.utils.Indenter;
import 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 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

@@ -1,13 +1,20 @@
package j2html.tags;
import j2html.Config;
import j2html.attributes.Attribute;
import j2html.rendering.TagBuilder;
import j2html.rendering.FlatHtml;
import j2html.rendering.HtmlBuilder;
import j2html.rendering.IndentedHtml;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class ContainerTag extends Tag<ContainerTag> {
public class ContainerTag<T extends ContainerTag<T>> extends Tag<T> {
private List<DomContent> children;
protected List<DomContent> children;
public ContainerTag(String tagName) {
super(tagName);
@@ -21,15 +28,15 @@ public class ContainerTag extends Tag<ContainerTag> {
* @param child DomContent-object to be appended
* @return itself for easy chaining
*/
public ContainerTag with(DomContent child) {
public T with(DomContent child) {
if (this == child) {
throw new RuntimeException("Cannot append a tag to itself.");
}
if (child == null) {
return this; // in some cases, like when using iff(), we ignore null children
if (child != null) {
// in some cases, like when using iff(), we ignore null children
children.add(child);
}
children.add(child);
return this;
return self();
}
@@ -41,8 +48,8 @@ public class ContainerTag extends Tag<ContainerTag> {
* @param child DomContent-object to be appended if condition met
* @return itself for easy chaining
*/
public ContainerTag condWith(boolean condition, DomContent child) {
return condition ? this.with(child) : this;
public T condWith(boolean condition, DomContent child) {
return condition ? this.with(child) : self();
}
@@ -52,13 +59,13 @@ public class ContainerTag extends Tag<ContainerTag> {
* @param children DomContent-objects to be appended
* @return itself for easy chaining
*/
public ContainerTag with(Iterable<? extends DomContent> children) {
public T with(Iterable<? extends DomContent> children) {
if (children != null) {
for (DomContent child : children) {
this.with(child);
}
}
return this;
return self();
}
@@ -70,8 +77,8 @@ public class ContainerTag extends Tag<ContainerTag> {
* @param children DomContent-objects to be appended if condition met
* @return itself for easy chaining
*/
public ContainerTag condWith(boolean condition, Iterable<? extends DomContent> children) {
return condition ? this.with(children) : this;
public T condWith(boolean condition, Iterable<? extends DomContent> children) {
return condition ? this.with(children) : self();
}
@@ -81,11 +88,23 @@ public class ContainerTag extends Tag<ContainerTag> {
* @param children DomContent-objects to be appended
* @return itself for easy chaining
*/
public ContainerTag with(DomContent... children) {
public T with(DomContent... children) {
for (DomContent child : children) {
with(child);
}
return this;
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();
}
@@ -97,8 +116,8 @@ public class ContainerTag extends Tag<ContainerTag> {
* @param children DomContent-objects to be appended if condition met
* @return itself for easy chaining
*/
public ContainerTag condWith(boolean condition, DomContent... children) {
return condition ? this.with(children) : this;
public T condWith(boolean condition, DomContent... children) {
return condition ? this.with(children) : self();
}
@@ -108,7 +127,7 @@ public class ContainerTag extends Tag<ContainerTag> {
* @param text the text to be appended
* @return itself for easy chaining
*/
public ContainerTag withText(String text) {
public T withText(String text) {
return with(new Text(text));
}
@@ -128,42 +147,40 @@ public class ContainerTag extends Tag<ContainerTag> {
*/
public String renderFormatted() {
try {
return renderFormatted(0);
} catch (IOException e) {
return render(IndentedHtml.into(new StringBuilder(), Config.global())).toString();
}catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private String renderFormatted(int lvl) throws IOException {
StringBuilder sb = new StringBuilder();
renderOpenTag(sb, null);
sb.append("\n");
if (!children.isEmpty()) {
for (DomContent c : children) {
lvl++;
if (c instanceof ContainerTag) {
sb.append(Config.indenter.indent(lvl, ((ContainerTag) c).renderFormatted(lvl)));
} else {
sb.append(Config.indenter.indent(lvl, c.render())).append("\n");
}
lvl--;
@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();
}
sb.append(Config.indenter.indent(lvl, ""));
renderCloseTag(sb);
sb.append("\n");
return sb.toString();
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 {
renderOpenTag(writer, model);
if (children != null && !children.isEmpty()) {
for (DomContent child : children) {
child.renderModel(writer, model);
}
}
renderCloseTag(writer);
}
HtmlBuilder<?> builder = (writer instanceof HtmlBuilder)
? (HtmlBuilder<?>) writer
: FlatHtml.into(writer, Config.global());
render(builder, model);
}
}

View File

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

View File

@@ -4,14 +4,21 @@ public class DomContentJoiner {
public static UnescapedText join(CharSequence delimiter, boolean fixPeriodAndCommaSpacing, Object... stringOrDomObjects) {
StringBuilder sb = new StringBuilder();
for (Object o : stringOrDomObjects) {
for (int i = 0; i < stringOrDomObjects.length; i++) {
Object o = stringOrDomObjects[i];
if (o instanceof String) {
sb.append(((String) o).trim()).append(delimiter);
sb.append(((String) o).trim());
} else if (o instanceof DomContent) {
sb.append(((DomContent) o).render().trim()).append(delimiter);
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) {

View File

@@ -0,0 +1,42 @@
package j2html.tags;
import j2html.Config;
import j2html.attributes.Attribute;
import j2html.rendering.TagBuilder;
import j2html.rendering.FlatHtml;
import 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 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

@@ -4,15 +4,14 @@ import j2html.Config;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Scanner;
import static j2html.TagCreator.rawHtml;
import static j2html.TagCreator.script;
import static j2html.TagCreator.style;
public class InlineStaticResource {
public enum TargetFormat {CSS_MIN, CSS, JS_MIN, JS}
public static ContainerTag get(String path, TargetFormat format) {
public static ContainerTag<? extends Tag<?>> get(String path, TargetFormat format) {
String fileString = getFileAsString(path);
switch (format) {
case CSS_MIN:
@@ -45,5 +44,7 @@ public class InlineStaticResource {
return s.hasNext() ? s.next() : "";
}
public enum TargetFormat {CSS_MIN, CSS, JS_MIN, JS}
}

View File

@@ -0,0 +1,81 @@
package j2html.tags;
import j2html.Config;
import j2html.rendering.FlatHtml;
import 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 j2html.tags;
import j2html.attributes.Attr;
import 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 j2html.tags;
import j2html.Config;
import j2html.rendering.FlatHtml;
import 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 j2html.tags;
import j2html.Config;
import j2html.rendering.FlatHtml;
import 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,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("accept", accept_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("action", action_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("alt", alt_);
}
return self();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("charset", charset_);
}
return self();
}
}

View File

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

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("cite", cite_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("cols", cols_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("colspan", colspan_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("content", content_);
}
return self();
}
}

View File

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

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("coords", coords_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("data", data_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("datetime", datetime_);
}
return self();
}
}

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("dirname", dirname_);
}
return self();
}
}

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("enctype", enctype_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("for", for_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("form", form_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("formaction", formaction_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("headers", headers_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("height", height_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("high", high_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("href", href_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("hreflang", hreflang_);
}
return self();
}
}

View File

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

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("kind", kind_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("label", label_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("list", list_);
}
return self();
}
}

View File

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

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("low", low_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import 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_) {
if (enable) {
self().attr("max", max_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import j2html.tags.Tag;
public interface IMaxlength<T extends Tag<T>> extends IInstance<T> {
default T withMaxlength(final String maxlength_) {
return self().attr("maxlength", maxlength_);
}
default T withCondMaxlength(final boolean enable, final String maxlength_) {
if (enable) {
self().attr("maxlength", maxlength_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import j2html.tags.Tag;
public interface IMedia<T extends Tag<T>> extends IInstance<T> {
default T withMedia(final String media_) {
return self().attr("media", media_);
}
default T withCondMedia(final boolean enable, final String media_) {
if (enable) {
self().attr("media", media_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import j2html.tags.Tag;
public interface IMethod<T extends Tag<T>> extends IInstance<T> {
default T withMethod(final String method_) {
return self().attr("method", method_);
}
default T withCondMethod(final boolean enable, final String method_) {
if (enable) {
self().attr("method", method_);
}
return self();
}
}

View File

@@ -0,0 +1,17 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import j2html.tags.Tag;
public interface IMin<T extends Tag<T>> extends IInstance<T> {
default T withMin(final String min_) {
return self().attr("min", min_);
}
default T withCondMin(final boolean enable, final String min_) {
if (enable) {
self().attr("min", min_);
}
return self();
}
}

View File

@@ -0,0 +1,18 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import j2html.tags.Tag;
public interface IMultiple<T extends Tag<T>> extends IInstance<T> {
default T isMultiple() {
self().attr("multiple");
return self();
}
default T withCondMultiple(final boolean enable) {
if (enable) {
self().attr("multiple");
}
return self();
}
}

View File

@@ -0,0 +1,18 @@
package j2html.tags.attributes;
import j2html.tags.IInstance;
import j2html.tags.Tag;
public interface IMuted<T extends Tag<T>> extends IInstance<T> {
default T isMuted() {
self().attr("muted");
return self();
}
default T withCondMuted(final boolean enable) {
if (enable) {
self().attr("muted");
}
return self();
}
}

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