├── .github └── workflows │ ├── greetings.yml │ ├── javadoc.yml │ ├── maven.yml │ └── notify_on_pull_request_open.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.api.md ├── README.cli.md ├── README.desktop.md ├── README.md ├── illustrations ├── html_java_js.png ├── intellij-maven-runner-configuration.png ├── jsgenerator_intro.png ├── sample.html └── screenshot_current_desktop_version.png ├── jsgenerator-api └── pom.xml ├── jsgenerator-cli └── pom.xml ├── jsgenerator-core ├── pom.xml └── src │ └── main │ ├── java │ ├── com │ │ └── osscameroon │ │ │ └── jsgenerator │ │ │ └── core │ │ │ ├── BuiltinVariableNameStrategy.java │ │ │ ├── Configuration.java │ │ │ ├── Converter.java │ │ │ ├── OutputStreamResolver.java │ │ │ ├── VariableDeclaration.java │ │ │ ├── VariableNameStrategy.java │ │ │ ├── autoconfigure │ │ │ └── JsGeneratorCoreAutoconfigure.java │ │ │ └── internal │ │ │ ├── ConverterDefault.java │ │ │ ├── InlineOutputStreamResolver.java │ │ │ ├── PathOutputStreamResolver.java │ │ │ ├── RandomVariableNameStrategy.java │ │ │ ├── StdinOutputStreamResolver.java │ │ │ └── TypeBasedVariableNameStrategy.java │ └── module-info.java │ └── resources │ └── META-INF │ └── spring.factories ├── jsgenerator-desktop ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ ├── com │ │ └── osscameroon │ │ │ └── jsgenerator │ │ │ └── desktop │ │ │ ├── autoconfigure │ │ │ └── JsGeneratorDesktop.java │ │ │ └── controller │ │ │ ├── FxmlNavigator.java │ │ │ ├── FxmlResolver.java │ │ │ └── HelloViewController.java │ └── module-info.java │ └── resources │ ├── application.yml │ ├── banner.txt │ └── com │ └── osscameroon │ └── jsgenerator │ └── desktop │ └── controller │ └── hello-view.fxml ├── jsgenerator-slim-api ├── pom.xml └── src │ └── main │ ├── java │ ├── com │ │ └── osscameroon │ │ │ └── jsgenerator │ │ │ └── api │ │ │ ├── JsGeneratorApi.java │ │ │ ├── domain │ │ │ ├── InlineOptions.java │ │ │ ├── MultipartOptions.java │ │ │ ├── Options.java │ │ │ └── Output.java │ │ │ └── rest │ │ │ ├── ConvertController.java │ │ │ └── Reply.java │ └── module-info.java │ └── resources │ ├── META-INF │ └── spring.factories │ ├── application.yaml │ └── banner.txt ├── jsgenerator-slim-cli ├── pom.xml └── src │ └── main │ ├── java │ ├── com │ │ └── osscameroon │ │ │ └── jsgenerator │ │ │ └── cli │ │ │ ├── Command.java │ │ │ ├── JsGeneratorCli.java │ │ │ ├── Valid.java │ │ │ └── internal │ │ │ └── CommandDefault.java │ └── module-info.java │ └── resources │ ├── application.yaml │ └── banner.txt ├── jsgenerator-test ├── jsgenerator-test-api │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── module-info.java │ │ └── test │ │ ├── java │ │ ├── com │ │ │ └── osscameroon │ │ │ │ └── jsgenerator │ │ │ │ └── test │ │ │ │ └── api │ │ │ │ ├── JsGeneratorApiTest.java │ │ │ │ └── helper │ │ │ │ └── MultipartResultMatcher.java │ │ └── module-info.java │ │ └── resources │ │ ├── htmlFilesInput │ │ ├── sample.html │ │ └── sampleWithComment.html │ │ └── jsFilesOutput │ │ ├── querySelectorAdded │ │ ├── commentConversionModeActivated │ │ │ └── sample.js │ │ ├── commentConversionModeNotActivated │ │ │ └── sample.js │ │ └── sample.js │ │ └── querySelectorNotAdded │ │ ├── commentConversionModeActivated │ │ └── sample.js │ │ ├── commentConversionModeNotActivated │ │ └── sample.js │ │ └── sample.js ├── jsgenerator-test-core │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── module-info.java │ │ └── test │ │ ├── java │ │ ├── com │ │ │ └── osscameroon │ │ │ │ └── jsgenerator │ │ │ │ └── test │ │ │ │ └── core │ │ │ │ └── ConverterTest.java │ │ └── module-info.java │ │ └── resources │ │ └── htmlFilesInput │ │ ├── sample.html │ │ └── sampleWithComment.html ├── jsgenerator-test-desktop │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── module-info.java │ │ └── test │ │ └── java │ │ ├── com │ │ └── osscameroon │ │ │ └── jsgenerator │ │ │ └── test │ │ │ └── desktop │ │ │ └── JsGeneratorDesktopTest.java │ │ └── module-info.java └── pom.xml └── pom.xml /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | # The following commented line to should not be used in order to avoid security issues such as "Resource not accessible by integration", this article give some explanations: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ 3 | #on: [pull_request, issues] 4 | on: 5 | issues: 6 | types: [opened] 7 | pull_request_target: 8 | types: [opened] 9 | jobs: 10 | greeting: 11 | runs-on: ubuntu-latest 12 | #permissions: 13 | #pull-requests: write 14 | #issues: write 15 | steps: 16 | - uses: actions/first-interaction@v1 17 | # At this moment, this GitHub Action has an issue: https://github.com/actions/first-interaction/issues/101 18 | # In order to try to solve this, we use this: https://github.com/keploy/keploy/pull/182/files 19 | # continue-on-error: true 20 | # Finally, this issue is solved by this PR https://github.com/actions/first-interaction/pull/103 so no need to use that condition anymore 21 | with: 22 | repo-token: ${{ secrets.GITHUB_TOKEN }} 23 | issue-message: 'Thanks for opening your first issue 😊 ! We really appreciate your work. Happy Coding 🎉🎊 !' 24 | pr-message: 'Thanks for opening your first pull request 😊 ! We really appreciate your work. Happy Coding 🎉🎊 !' 25 | -------------------------------------------------------------------------------- /.github/workflows/javadoc.yml: -------------------------------------------------------------------------------- 1 | name: Maven Site 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: 7 | - '.github/workflows/javadoc.yml' 8 | - 'jsgenerator-core/**' 9 | - 'pom.xml' 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up JDK 21 19 | uses: actions/setup-java@v4 20 | with: 21 | distribution: 'temurin' 22 | java-version: '21' 23 | # Given the fact that this is a multimodule project, build process will take long time so we activate caching 24 | # To know more: https://maven.apache.org/extensions/maven-build-cache-extension/cache.html 25 | cache: 'maven' 26 | - name: Build Javadoc Site with Maven 27 | #To see the full stack trace of the errors, re-run Maven with the -e switch. 28 | #Re-run Maven using the -X switch to enable full debug logging. 29 | # -B,--batch-mode Run in non-interactive (batch) mode (disables output color) 30 | # To learn more about options: https://maven.apache.org/ref/3.6.3/maven-embedder/cli.html 31 | run: | 32 | mvn clean site -B -e -X --projects 'jsgenerator-core' 33 | env: 34 | MAVEN_SITE_GITHUB_OAUTH_TOKEN: ${{ secrets.MAVEN_SITE_GITHUB_OAUTH_TOKEN }} 35 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | paths-ignore: 10 | - '**.md' 11 | pull_request: 12 | branches: [ main ] 13 | paths-ignore: 14 | - '**.md' 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | build: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Set up JDK 21 27 | uses: actions/setup-java@v4 28 | with: 29 | distribution: 'temurin' 30 | java-version: '21' 31 | # Given the fact that this is a multimodule project, build process will take long time so we activate caching 32 | # To know more: https://maven.apache.org/extensions/maven-build-cache-extension/cache.html 33 | cache: 'maven' 34 | - name: Build with Maven 35 | #To see the full stack trace of the errors, re-run Maven with the -e switch. 36 | #Re-run Maven using the -X switch to enable full debug logging. 37 | # -B,--batch-mode Run in non-interactive (batch) mode (disables output color) 38 | # To learn more about options: https://maven.apache.org/ref/3.6.3/maven-embedder/cli.html 39 | run: | 40 | mvn clean test -B -e -X 41 | -------------------------------------------------------------------------------- /.github/workflows/notify_on_pull_request_open.yml: -------------------------------------------------------------------------------- 1 | name: notify of pull_request creation 2 | on: 3 | pull_request_target: 4 | types: [ opened ] 5 | branches: 6 | - main 7 | 8 | jobs: 9 | notify: 10 | uses: osscameroon/global-github-actions/.github/workflows/notify_on_pull_request_open.yaml@main 11 | secrets: 12 | telegram_channel_id: ${{ secrets.TELEGRAM_OSSCAMEROON_CHANNEL_ID }} 13 | telegram_token: ${{ secrets.TELEGRAM_BOT_TOKEN }} 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.jsgenerator.js 2 | *.build_artifacts.txt 3 | dependency-reduced-pom.xml 4 | /jsgenerator* 5 | !/jsgenerator-core 6 | !/jsgenerator-desktop 7 | !/jsgenerator-test 8 | !/jsgenerator-slim-api 9 | !/jsgenerator-slim-cli 10 | !/jsgenerator-api 11 | !/jsgenerator-cli 12 | !/jsgenerator-web 13 | 14 | # Compiled class file 15 | *.class 16 | 17 | # Log file 18 | *.log 19 | 20 | # BlueJ files 21 | *.ctxt 22 | 23 | # Mobile Tools for Java (J2ME) 24 | .mtj.tmp/ 25 | 26 | # Package Files # 27 | *.jar 28 | *.war 29 | *.nar 30 | *.ear 31 | *.zip 32 | *.tar.gz 33 | *.rar 34 | 35 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 36 | hs_err_pid* 37 | 38 | HELP.md 39 | target/ 40 | !.mvn/wrapper/maven-wrapper.jar 41 | !**/src/main/** 42 | !**/src/test/** 43 | mvnw 44 | mvnw.cmd 45 | .mvn/ 46 | 47 | ### STS / Eclipse ### 48 | .apt_generated 49 | .classpath 50 | .factorypath 51 | .project 52 | .settings 53 | .springBeans 54 | .sts4-cache 55 | 56 | ### IntelliJ IDEA ### 57 | .idea 58 | *.iws 59 | *.iml 60 | *.ipr 61 | 62 | ### NetBeans ### 63 | /nbproject/private/ 64 | /nbbuild/ 65 | /dist/ 66 | /nbdist/ 67 | /.nb-gradle/ 68 | build/ 69 | 70 | ### VS Code ### 71 | .vscode/ 72 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | CODE_OF_CONDUCT.md. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | ```shell 4 | # 1. Clone 5 | git clone git@github.com:osscameroon/js-generator.git 6 | 7 | # 2. Move to root directory 8 | cd js-generator 9 | 10 | # 3. Tests & Build 11 | mvn clean package 12 | 13 | # 4. Browse through code 14 | # 5. Run CLI with --help and play with it 15 | # 6. Fork the project, build, test, open a pull request 16 | ``` 17 | 18 | Thanks for your commitment, we really appreciate! 19 | Happy Coding! 😊🎉💯 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 OSS Cameroon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.api.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | After starting the `jsgenerator-api` as described in the [README.md](./README.md), you can read: 4 | 5 | + OpenAPI spec. at: [http://localhost:8080/openapi.yaml](http://localhost:8080/openapi.yaml) 6 | + OpenAPI UI at: [http://localhost:8080](http://localhost:8080) 7 | 8 | Two endpoints are exposed: 9 | + `POST /convert` 10 | + `POST /convert/files` 11 | 12 | Both accept options as follow: 13 | ```json 14 | { 15 | "targetElementSelector": ":root > body", 16 | "pattern": "inline-filename-pattern", 17 | "variableNameStrategy": "TYPE_BASED", 18 | "variableDeclaration": "LET", 19 | "extension": ".jsgenerator.js", 20 | "commentConversionModeActivated": true, 21 | "querySelectorAdded": true, 22 | "contents": [ 23 | "string" 24 | ] 25 | } 26 | ``` 27 | > **NOTE:** The `"contents"` field is mandatory for `POST /convert` and forbidden for `POST /convert/files` 28 | 29 | 30 | --- 31 | 32 | Using [`curl`](https://curl.se/): 33 | 34 | + `POST /convert` 35 | ```shell 36 | # You can also pass as many HTML content as you want 37 | # Response will be of 'application/json' content type 38 | curl -H 'content-type: application/json' -X POST --data '{"contents": ["
js-jsgenerator
"]}' http://localhost:8080/convert 39 | 40 | #Response 41 | {"content":[{"filename":"inline.0.jsgenerator.js","content":"let targetElement_001 = document.querySelector(`:root > body`);\r\n\r\n\r\nlet div_001 = document.createElement('div');\r\nlet text_001 = document.createTextNode(`js-generator`);\r\ndiv_001.appendChild(text_001);\r\ntargetElement_001.appendChild(div_001);\r\n"}],"status":"SUCCESS"} 42 | ``` 43 | 44 | + `POST /convert/files` 45 | ```shell 46 | # You can call the API with multiple **files** and at most one **options** 47 | # Response will be of 'multipart/form-data' content type 48 | curl -s -X POST -H 'content-type: multipart/form-data' -F files=@illustrations/sample.html -F "options={ \"querySelectorAdded\": true, \"variableDeclaration\": \"VAR\" }; type=application/json" http://localhost:8080/convert/files 49 | 50 | # -s flag is added in order to prevent curl to mix response and progress meter 51 | #if not added, this will happen: 100 5280 100 4275 100 1005 117k 28194 --:--:-- --:--:-- --:--:-- 147kment.createTextNode(` `); 52 | 53 | #Response 54 | 55 | --d2a-7NlH3rlmcFC3loiJxDxom6iojCunhkzzH 56 | Content-Disposition: form-data; name="inline.0.jsgenerator.js" 57 | Content-Type: application/octet-stream 58 | Content-Length: 4069 59 | 60 | var targetElement_001 = document.querySelector(`:root > body`); 61 | 62 | 63 | var html_001 = document.createElement('html'); 64 | var text_001 = document.createTextNode(` `); 65 | html_001.appendChild(text_001); 66 | 67 | var head_001 = document.createElement('head'); 68 | var text_002 = document.createTextNode(` `); 69 | head_001.appendChild(text_002); 70 | 71 | var meta_001 = document.createElement('meta'); 72 | meta_001.setAttribute(`charset`, `utf-8`); 73 | head_001.appendChild(meta_001); 74 | var text_003 = document.createTextNode(` `); 75 | head_001.appendChild(text_003); 76 | 77 | var title_001 = document.createElement('title'); 78 | var text_004 = document.createTextNode(`Sample`); 79 | title_001.appendChild(text_004); 80 | head_001.appendChild(title_001); 81 | var text_005 = document.createTextNode(` `); 82 | head_001.appendChild(text_005); 83 | 84 | var link_001 = document.createElement('link'); 85 | link_001.setAttribute(`rel`, `stylesheet`); 86 | link_001.setAttribute(`href`, ``); 87 | head_001.appendChild(link_001); 88 | var text_006 = document.createTextNode(` `); 89 | head_001.appendChild(text_006); 90 | html_001.appendChild(head_001); 91 | var text_007 = document.createTextNode(` `); 92 | html_001.appendChild(text_007); 93 | 94 | var body_001 = document.createElement('body'); 95 | var text_008 = document.createTextNode(` `); 96 | body_001.appendChild(text_008); 97 | 98 | var div_001 = document.createElement('div'); 99 | div_001.setAttribute(`id`, `container`); 100 | var text_009 = document.createTextNode(` `); 101 | div_001.appendChild(text_009); 102 | 103 | var div_002 = document.createElement('div'); 104 | div_002.setAttribute(`id`, `header`); 105 | var text_010 = document.createTextNode(` `); 106 | div_002.appendChild(text_010); 107 | 108 | var h1_001 = document.createElement('h1'); 109 | var text_011 = document.createTextNode(`Sample`); 110 | h1_001.appendChild(text_011); 111 | div_002.appendChild(h1_001); 112 | var text_012 = document.createTextNode(` `); 113 | div_002.appendChild(text_012); 114 | 115 | var img_001 = document.createElement('img'); 116 | img_001.setAttribute(`src`, `kanye.jpg`); 117 | img_001.setAttribute(`alt`, `kanye`); 118 | div_002.appendChild(img_001); 119 | var text_013 = document.createTextNode(` `); 120 | div_002.appendChild(text_013); 121 | div_001.appendChild(div_002); 122 | var text_014 = document.createTextNode(` `); 123 | div_001.appendChild(text_014); 124 | 125 | var div_003 = document.createElement('div'); 126 | div_003.setAttribute(`id`, `main`); 127 | var text_015 = document.createTextNode(` `); 128 | div_003.appendChild(text_015); 129 | 130 | var h2_001 = document.createElement('h2'); 131 | var text_016 = document.createTextNode(`Main`); 132 | h2_001.appendChild(text_016); 133 | div_003.appendChild(h2_001); 134 | var text_017 = document.createTextNode(` `); 135 | div_003.appendChild(text_017); 136 | 137 | var p_001 = document.createElement('p'); 138 | var text_018 = document.createTextNode(`This is the main content.`); 139 | p_001.appendChild(text_018); 140 | div_003.appendChild(p_001); 141 | var text_019 = document.createTextNode(` `); 142 | div_003.appendChild(text_019); 143 | 144 | var img_002 = document.createElement('img'); 145 | img_002.setAttribute(`src`, ``); 146 | img_002.setAttribute(`alt`, ``); 147 | div_003.appendChild(img_002); 148 | var text_020 = document.createTextNode(` `); 149 | div_003.appendChild(text_020); 150 | div_001.appendChild(div_003); 151 | var text_021 = document.createTextNode(` `); 152 | div_001.appendChild(text_021); 153 | 154 | var div_004 = document.createElement('div'); 155 | div_004.setAttribute(`id`, `footer`); 156 | var text_022 = document.createTextNode(` `); 157 | div_004.appendChild(text_022); 158 | 159 | var p_002 = document.createElement('p'); 160 | var text_023 = document.createTextNode(`Copyright - 2019`); 161 | p_002.appendChild(text_023); 162 | div_004.appendChild(p_002); 163 | var text_024 = document.createTextNode(` `); 164 | div_004.appendChild(text_024); 165 | div_001.appendChild(div_004); 166 | var text_025 = document.createTextNode(` `); 167 | div_001.appendChild(text_025); 168 | body_001.appendChild(div_001); 169 | var text_026 = document.createTextNode(` `); 170 | body_001.appendChild(text_026); 171 | html_001.appendChild(body_001); 172 | targetElement_001.appendChild(html_001); 173 | 174 | --d2a-7NlH3rlmcFC3loiJxDxom6iojCunhkzzH-- 175 | ``` 176 | -------------------------------------------------------------------------------- /README.cli.md: -------------------------------------------------------------------------------- 1 | # Command Line Interface 2 | 3 | ```shell 4 | let targetElement_001 = document.querySelector(`:root > body`); 5 | 6 | 7 | let div_001 = document.createElement('div'); 8 | let text_001 = document.createTextNode(`I am a `); 9 | div_001.appendChild(text_001); 10 | 11 | let strong_001 = document.createElement('strong'); 12 | let text_002 = document.createTextNode(`tea pot`); 13 | strong_001.appendChild(text_002); 14 | div_001.appendChild(strong_001); 15 | targetElement_001.appendChild(div_001); 16 | ``` 17 | 18 | --- 19 | 20 | `jsgenerator` has several options that can be used in a console here is an example of use below 21 | 22 | ```text 23 | Usage: jsgenerator [-chtV] [-qs] [-e=] 24 | [--inline-pattern=] 25 | [-k=] [--path-pattern=] 26 | [-s=] 27 | [--stdin-pattern=] 28 | [--variable-name-generation-strategy=] [-i=...]... [...] 30 | Translating files, stdin or inline from HTML to JS 31 | [...] file paths to translate content, parsed as HTML 32 | -c, --comment optional comments 33 | -e, --ext= output files' extension 34 | -h, --help Show this help message and exit. 35 | -i, --inline=... 36 | args as HTML content, not files 37 | --inline-pattern= 38 | Pattern for inline output filename 39 | -k, --keyword= 40 | variable declaration keyword 41 | --path-pattern= 42 | pattern for path-based output filenames 43 | -qs, --query-selector 44 | What the browser renders depends on whether "document. 45 | querySelector(':root > body')" is added to the 46 | output. If added, the browser will render the 47 | output successfully, it is useful for debugging 48 | purpose, 49 | to verify that the js output matches what the 50 | html input does. 51 | If not, if the user tries to run the output as 52 | it is then the browser will not be able to render, 53 | it will show a blank page. 54 | So, it depends on what the user wants to do with 55 | the output. 56 | "https://jsfiddle.net/", "https://codepen. 57 | io/pen/" and Browser Console help to give a quick 58 | feedback. 59 | 60 | -s, --selector= 61 | Target element selector 62 | --stdin-pattern= 63 | pattern for stdin output filenames 64 | -t, --tty output to stdin, not files 65 | -V, --version Print version information and exit. 66 | --variable-name-generation-strategy= 67 | Variable names generation strategy 68 | ``` 69 | -------------------------------------------------------------------------------- /README.desktop.md: -------------------------------------------------------------------------------- 1 | > Screenshot of the current desktop version: 2 | > 3 | > ![Screenshot of the current desktop version](illustrations/screenshot_current_desktop_version.png) 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Maven Build](https://github.com/osscameroon/js-generator/actions/workflows/maven.yml/badge.svg) 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | [![Contributors](https://img.shields.io/github/contributors-anon/osscameroon/js-generator)](https://github.com/osscameroon/js-generator/graphs/contributors) 5 | ![Follow](https://img.shields.io/twitter/follow/osscameroon?style=social) 6 | 7 | # Status: UNDER DEVELOPMENT 8 | 9 | # Table of Contents 10 | - [About](#about) 11 | - [Getting Started](#getting-started) 12 | - [Requirements](#requirements) 13 | - [Modules](#modules) 14 | - [Architecture](#architecture) 15 | - [Compiling](#compiling) 16 | - [Running](#running) 17 | - [Packaging](#packaging) 18 | - [Contribute](#contribute) 19 | 20 | ![From Html to Js through Java](illustrations/html_java_js.png) 21 | 22 | # About 23 | 24 | Translating from HTML to JS 25 | 26 | > This project is different from the 27 | > [JavaScript Generator Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator). 28 | 29 | The goal is to generate JS from HTML following the [Document Object Model](https://www.w3schools.com/js/js_htmldom.asp) structure. Sometimes, we forget how to write 30 | JavaScript to build dynamic web apps. Even if we know JS, it happens that we don't always have enough time to generate 31 | JS from a big HTML code. Thus, the goal of this project is helping developers gaining time by producing JS code as 32 | Output based on HTML as Input. This project will be very useful for beginners learning HTML and JavaScript. Also, it 33 | will help more experienced developers whenever they want to use JS instead of HTML, very useful in applications where we need to add dynamism. 34 | 35 | [Sherlock Wisdom](https://github.com/sherlockwisdom) shared why he needed such tool: 36 | 37 | > 😂 Yes it's hard to say why it's important. I was working on an Android based app, but was building it with Vanilla JavaScript. So I needed a quick way to turn bootstrap code into Vanilla Js objects so that I could do what ReactJS does now 🤣. This was ~4 years back. Not sure of its relevance now, but they could be some. 😅 Sorry if I rather made things not easy for you to explain. 38 | 39 | We would like to give credit to [jsoup](https://jsoup.org/) / [jsoup GitHub Repository](https://github.com/jhy/jsoup/) as the main library to help us handle HTML tokenization and traversing. 40 | 41 | ![How does it work in a nutshell ?](illustrations/jsgenerator_intro.png) 42 | 43 | # Getting Started 44 | 45 | ## Requirements 46 | 47 | + JDK 21 48 | + Maven 4 49 | > Because of its unique features over maven 3: 50 | > namely, multi module dependency resolution under common parent, when running a maven goal only on some child 51 | + Spring Boot 3.3.1 52 | > Leverage convention over configuration and autoconfiguration discovery to enforce consistent a behaviour 53 | > throughout our frontends 54 | 55 | ## Modules 56 | 57 | The project takes advantage of Maven multimodule capabilities to enforce a consistent versioning and releases and, 58 | the specific Maven 4 features to deliver a seamless developer experience. 59 | 60 | ```text 61 | js-generator: 62 | |- jsgenerator-api 63 | |- jsgenerator-web 64 | |- jsgenerator-cli 65 | \- jsgenerator-desktop 66 | ``` 67 | 68 | ## Architecture 69 | 70 | | THE MODULE | ITS CONTENT && DEPENDENCIES | PACKAGING | 71 | |------------------------------------|-------------------------------------|-------| 72 | | js-generator | Bill of Material, global properties | POM | 73 | | jsgenerator-core | Core API, Spring Boot auto-conf | JAR | 74 | | jsgenerator-slim-api | jsgenerator-core, spring-web | JAR | 75 | | jsgenerator-slim-cli | jsgenerator-core, picocli | JAR | 76 | | [jsgenerator-api](./README.api.md) | jsgenerator-slim-api | FAT JAR | 77 | | [jsgenerator-cli](./README.cli.md) | jsgenerator-slim-cli | FAT JAR | 78 | | [jsgenerator-desktop](./README.desktop.md) | jsgenerator-core, javafx-fxml | JAR | 79 | 80 | > **NOTE:** FAT JAR packaged modules are mere wrappers around slim modules. The separation is important because then, 81 | > the test modules can use slim JARs as dependencies, unlike FAT JARs. This has to do with how "normal" vs. FAT JARs 82 | > are laid out. 83 | 84 | ## Compiling 85 | 86 | ```shell 87 | # Clone the git repository 88 | git clone git@github.com:osscameroon/js-generator.git 89 | 90 | # Move at the project root 91 | cd js-generator 92 | 93 | # Compile & test all the modules 94 | mvn clean test 95 | ``` 96 | 97 | ## Running 98 | 99 | > Compiling the whole project before running child modules is advised. 100 | > 101 | > To set up you IDE runner, follow this IntelliJ example: 102 | > 103 | > ![](illustrations/intellij-maven-runner-configuration.png) 104 | 105 | API Server : [jsgenerator-api](./README.api.md) 106 | ```shell 107 | # After starting the server, visit http://localhost:8080 108 | mvn --also-make --projects jsgenerator-api clean spring-boot:run 109 | ``` 110 | 111 | Command Line Interface (CLI) : [jsgenerator-cli](./README.cli.md) 112 | ```shell 113 | # After reading the help, play out with different CLI options 114 | mvn --also-make --projects jsgenerator-cli clean spring-boot:run -Dspring-boot.run.arguments=--help 115 | 116 | # For example: 117 | mvn --also-make --projects jsgenerator-cli clean spring-boot:run -Dspring-boot.run.arguments="--tty --inline '
I am a tea pot
'" 118 | 119 | # It's also possible to create the jar first 120 | mvn clean package 121 | 122 | # then run the following commands and replace {version} by the current one (0.0.1-SNAPSHOT at this time) 123 | java -jar jsgenerator-cli/target/jsgenerator-cli-{version}.jar # java -jar jsgenerator-cli/target/jsgenerator-cli-0.0.1-SNAPSHOT.jar --help 124 | java -jar jsgenerator-cli/target/jsgenerator-cli-{version}.jar --tty --inline '
I am a tea pot
' 125 | ``` 126 | 127 | Desktop : [jsgenerator-desktop](./README.desktop.md) 128 | ```shell 129 | # Create the jar first 130 | mvn clean package 131 | 132 | # then run this command and replace {version} by the current one (0.0.1-SNAPSHOT at this time) 133 | java -jar jsgenerator-desktop/target/jsgenerator-desktop-{version}.jar # java -jar jsgenerator-desktop/target/jsgenerator-desktop-0.0.1-SNAPSHOT.jar 134 | ``` 135 | 136 | 137 | ## Packaging 138 | 139 | ```shell 140 | # Will compile all the modules into JAR (or FAT JAR - see the table above) 141 | mvn clean package 142 | ``` 143 | 144 | # Contribute 145 | 146 | All your contributions are welcome! 147 | 148 | Do not hesitate to open an issue on this repository and/or create a pull request (PR). 149 | 150 | In order to create a PR, just fork first. 151 | 152 | **[We started from the bottom 7 years ago](https://github.com/opensourcecameroon/jsGenerator), now we are here, we believe we will continue moving forward together 😊.** 153 | 154 | Thanks for your commitment, we really appreciate! 155 | Happy Coding! 😊🎉💯 156 | 157 |
158 | 159 | 160 |
161 | 162 | [Back To The Top](#table-of-contents) 163 | -------------------------------------------------------------------------------- /illustrations/html_java_js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osscameroon/js-generator/504c844491dcaf9662a3eef4bbec7244310fad32/illustrations/html_java_js.png -------------------------------------------------------------------------------- /illustrations/intellij-maven-runner-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osscameroon/js-generator/504c844491dcaf9662a3eef4bbec7244310fad32/illustrations/intellij-maven-runner-configuration.png -------------------------------------------------------------------------------- /illustrations/jsgenerator_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osscameroon/js-generator/504c844491dcaf9662a3eef4bbec7244310fad32/illustrations/jsgenerator_intro.png -------------------------------------------------------------------------------- /illustrations/sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sample 7 | 8 | 9 | 10 |
11 | 15 |
16 |

Main

17 |

This is the main content.

18 | 19 |
20 | 23 |
24 | 25 | -------------------------------------------------------------------------------- /illustrations/screenshot_current_desktop_version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osscameroon/js-generator/504c844491dcaf9662a3eef4bbec7244310fad32/illustrations/screenshot_current_desktop_version.png -------------------------------------------------------------------------------- /jsgenerator-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jsgenerator 7 | com.osscameroon 8 | ${revision} 9 | 10 | 4.0.0 11 | 12 | jsgenerator-api 13 | 14 | 15 | com.osscameroon.jsgenerator.api.JsGeneratorApi 16 | 17 | 18 | 19 | 20 | 21 | org.graalvm.buildtools 22 | native-maven-plugin 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-maven-plugin 27 | 28 | false 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | com.osscameroon 37 | jsgenerator-slim-api 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /jsgenerator-cli/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jsgenerator 7 | com.osscameroon 8 | ${revision} 9 | 10 | 4.0.0 11 | 12 | jsgenerator-cli 13 | 14 | 15 | com.osscameroon.jsgenerator.cli.JsGeneratorCli 16 | 17 | 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-maven-plugin 23 | 24 | false 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | com.osscameroon 33 | jsgenerator-slim-cli 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /jsgenerator-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jsgenerator 7 | com.osscameroon 8 | ${revision} 9 | 10 | jar 11 | 4.0.0 12 | 13 | jsgenerator-core 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/BuiltinVariableNameStrategy.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.core; 2 | 3 | import java.util.function.Supplier; 4 | 5 | public enum BuiltinVariableNameStrategy implements Supplier { 6 | TYPE_BASED(VariableNameStrategy::ofTypeBased), 7 | RANDOM(VariableNameStrategy::ofRandom), 8 | ; 9 | 10 | private final Supplier variableNameStrategySupplier; 11 | 12 | BuiltinVariableNameStrategy(final Supplier variableNameStrategySupplier) { 13 | this.variableNameStrategySupplier = variableNameStrategySupplier; 14 | } 15 | 16 | @Override 17 | public VariableNameStrategy get() { 18 | return variableNameStrategySupplier.get(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.core; 2 | 3 | import static com.osscameroon.jsgenerator.core.VariableDeclaration.LET; 4 | import static com.osscameroon.jsgenerator.core.VariableNameStrategy.ofTypeBased; 5 | 6 | public class Configuration { 7 | private static final String ROOT_BODY = ":root > body"; 8 | 9 | private String targetElementSelector = ROOT_BODY; 10 | /** 11 | * What the browser renders depends on whether we add document.querySelector(':root > body') to the output. 12 | * If added, the browser will render the output successfully, it is useful for debugging purpose, 13 | * to verify that the js output matches what the html input does. 14 | * If not, if the user tries to run the output as it is then the browser will not be able to render,it will show a blank page. 15 | * So, it depends on what the user wants to do with the output. 16 | * 17 | * @see JSFiddle, CodePen and Browser Console help to give a quick feedback. 18 | */ 19 | 20 | private boolean querySelectorAdded = true; 21 | private boolean commentConversionModeActivated = true; 22 | private VariableDeclaration variableDeclaration = LET; 23 | private VariableNameStrategy variableNameStrategy = ofTypeBased(); 24 | 25 | public Configuration() { 26 | } 27 | 28 | public Configuration(VariableDeclaration variableDeclaration, 29 | boolean querySelectorAdded, 30 | boolean commentConversionModeActivated) { 31 | this.querySelectorAdded = querySelectorAdded; 32 | this.variableDeclaration = variableDeclaration; 33 | this.commentConversionModeActivated = commentConversionModeActivated; 34 | } 35 | 36 | public Configuration(VariableDeclaration variableDeclaration, 37 | boolean querySelectorAdded) { 38 | this.querySelectorAdded = querySelectorAdded; 39 | this.variableDeclaration = variableDeclaration; 40 | } 41 | 42 | public Configuration(VariableDeclaration variableDeclaration, 43 | VariableNameStrategy variableNameStrategy, 44 | boolean querySelectorAdded, 45 | boolean commentConversionModeActivated) { 46 | this.querySelectorAdded = querySelectorAdded; 47 | this.variableDeclaration = variableDeclaration; 48 | this.variableNameStrategy = variableNameStrategy; 49 | this.commentConversionModeActivated = commentConversionModeActivated; 50 | } 51 | 52 | public Configuration(String targetElementSelector, 53 | boolean querySelectorAdded, 54 | boolean commentConversionModeActivated, 55 | VariableDeclaration variableDeclaration, 56 | VariableNameStrategy variableNameStrategy) { 57 | this.querySelectorAdded = querySelectorAdded; 58 | this.variableDeclaration = variableDeclaration; 59 | this.variableNameStrategy = variableNameStrategy; 60 | this.targetElementSelector = targetElementSelector; 61 | this.commentConversionModeActivated = commentConversionModeActivated; 62 | } 63 | 64 | public String getTargetElementSelector() { 65 | return targetElementSelector; 66 | } 67 | 68 | public boolean isQuerySelectorAdded() { 69 | return querySelectorAdded; 70 | } 71 | 72 | public boolean isCommentConversionModeActivated() { 73 | return commentConversionModeActivated; 74 | } 75 | 76 | public VariableDeclaration getVariableDeclaration() { 77 | return variableDeclaration; 78 | } 79 | 80 | public VariableNameStrategy getVariableNameStrategy() { 81 | return variableNameStrategy; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/Converter.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.core; 2 | 3 | import com.osscameroon.jsgenerator.core.internal.ConverterDefault; 4 | import org.springframework.lang.NonNull; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | 10 | @FunctionalInterface 11 | public interface Converter { 12 | default void convert(@NonNull final InputStream inputStream, @NonNull final OutputStream outputStream) throws IOException { 13 | convert(inputStream, outputStream, new Configuration()); 14 | } 15 | 16 | void convert(@NonNull final InputStream inputStream, @NonNull final OutputStream outputStream, @NonNull Configuration configuration) throws IOException; 17 | 18 | static Converter of() { 19 | return new ConverterDefault(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/OutputStreamResolver.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.core; 2 | 3 | import com.osscameroon.jsgenerator.core.internal.InlineOutputStreamResolver; 4 | import com.osscameroon.jsgenerator.core.internal.PathOutputStreamResolver; 5 | import com.osscameroon.jsgenerator.core.internal.StdinOutputStreamResolver; 6 | import org.springframework.lang.NonNull; 7 | 8 | import java.util.Map; 9 | 10 | @FunctionalInterface 11 | public interface OutputStreamResolver { 12 | String ORIGINAL_DIRECTORY = "original-directory"; 13 | String ORIGINAL_EXTENSION = "original-extension"; 14 | String ORIGINAL_BASENAME = "original-basename"; 15 | String EXTENSION = "extension"; 16 | String ORIGINAL = "original"; 17 | String INDEX = "index"; 18 | 19 | String resolve(@NonNull final String template, @NonNull final Map container); 20 | 21 | static OutputStreamResolver ofInline() { 22 | return new InlineOutputStreamResolver(); 23 | } 24 | 25 | static OutputStreamResolver ofStdin() { 26 | return new StdinOutputStreamResolver(); 27 | } 28 | 29 | static OutputStreamResolver ofPath() { 30 | return new PathOutputStreamResolver(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/VariableDeclaration.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.core; 2 | 3 | public enum VariableDeclaration { 4 | 5 | LET,VAR,CONST 6 | } 7 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/VariableNameStrategy.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.core; 2 | 3 | import com.osscameroon.jsgenerator.core.internal.RandomVariableNameStrategy; 4 | import com.osscameroon.jsgenerator.core.internal.TypeBasedVariableNameStrategy; 5 | import org.springframework.lang.NonNull; 6 | 7 | @FunctionalInterface 8 | public interface VariableNameStrategy { 9 | String nextName(@NonNull String type); 10 | 11 | static VariableNameStrategy ofRandom() { 12 | return new RandomVariableNameStrategy(); 13 | } 14 | 15 | static VariableNameStrategy ofTypeBased() { 16 | return new TypeBasedVariableNameStrategy(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/autoconfigure/JsGeneratorCoreAutoconfigure.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.core.autoconfigure; 2 | 3 | import com.osscameroon.jsgenerator.core.Converter; 4 | import com.osscameroon.jsgenerator.core.OutputStreamResolver; 5 | import org.springframework.boot.SpringBootConfiguration; 6 | import org.springframework.context.annotation.Bean; 7 | 8 | @SpringBootConfiguration 9 | public class JsGeneratorCoreAutoconfigure { 10 | @Bean 11 | public OutputStreamResolver pathOutputStreamResolver() { 12 | return OutputStreamResolver.ofPath(); 13 | } 14 | 15 | @Bean 16 | public OutputStreamResolver stdinOutputStreamResolver() { 17 | return OutputStreamResolver.ofStdin(); 18 | } 19 | 20 | @Bean 21 | public OutputStreamResolver inlineOutputStreamResolver() { 22 | return OutputStreamResolver.ofInline(); 23 | } 24 | 25 | @Bean 26 | public Converter converter() { 27 | return Converter.of(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/internal/ConverterDefault.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.core.internal; 2 | 3 | import com.osscameroon.jsgenerator.core.Configuration; 4 | import com.osscameroon.jsgenerator.core.Converter; 5 | import com.osscameroon.jsgenerator.core.VariableDeclaration; 6 | import org.jsoup.Jsoup; 7 | import org.jsoup.nodes.Attribute; 8 | import org.jsoup.nodes.Attributes; 9 | import org.jsoup.nodes.Comment; 10 | import org.jsoup.nodes.Element; 11 | import org.jsoup.nodes.Node; 12 | import org.jsoup.nodes.TextNode; 13 | 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.io.OutputStream; 17 | import java.io.OutputStreamWriter; 18 | import java.io.Writer; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.Scanner; 23 | 24 | import static java.lang.String.format; 25 | import static java.lang.String.join; 26 | import static org.jsoup.parser.Parser.xmlParser; 27 | 28 | public class ConverterDefault implements Converter { 29 | private static final List BOOLEAN_ATTRIBUTES = List.of("allowfullscreen", "async", "autofocus", 30 | "autoplay", "checked", "controls", "default", "defer", "disabled", "formnovalidate", "ismap", "itemscope", 31 | "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", 32 | "reversed", "selected", "truespeed", "contenteditable"); 33 | 34 | private static Element resolveClosestNonSelfClosingAncestor(Node element) { 35 | // NOTE: Fix issue #41 by looking up closest non-self-closing parent/ancestor to append current element to 36 | var ancestor = (Element) element.parent(); 37 | 38 | //noinspection ConstantConditions 39 | while (ancestor.tag().isSelfClosing()) { 40 | ancestor = ancestor.parent(); 41 | } 42 | 43 | return ancestor; 44 | } 45 | 46 | /* 47 | * TODO: There is some issue related to encoding, should we not set utf8 encoding here instead of setting it as we do inside ConverterTest ? 48 | * Make sure that every input has utf8 encoding. If not, we set this encoding. 49 | * 50 | * java define encoding of InputStream 51 | * https://stackoverflow.com/questions/3043710/java-inputstream-encoding-charset 52 | * 53 | * */ 54 | @Override 55 | public void convert(InputStream inputStream, OutputStream outputStream, Configuration configuration) throws IOException { 56 | final var stringBuilder = new StringBuilder(); 57 | final var scanner = new Scanner(inputStream); 58 | 59 | while (scanner.hasNext()) { 60 | stringBuilder.append(scanner.nextLine()); 61 | } 62 | 63 | final var content = stringBuilder.toString(); 64 | 65 | // NOTE: There is nothing to do 66 | if (content.isBlank()) return; 67 | 68 | final var variableNameStrategy = configuration.getVariableNameStrategy(); 69 | final var document = Jsoup.parse(content, xmlParser()); 70 | final var writer = new OutputStreamWriter(outputStream); 71 | 72 | final var selector = configuration.getTargetElementSelector(); 73 | 74 | 75 | final var variable = configuration.isQuerySelectorAdded() 76 | ? variableNameStrategy.nextName("targetElement") : null; 77 | 78 | 79 | // NOTE: We need a variable to keep track of ancestors name. 80 | // Following issue#41, elements that follow self-closing that should be added 81 | // to their real parent and not the JSoup-inferred parent (which happen to just 82 | // be their previous self-closing sibling) 83 | 84 | Map variables = new HashMap(); 85 | 86 | /* 87 | * if configuration.isQuerySelectorAdded() is true then variable = variableNameStrategy.nextName("targetElement") 88 | * if configuration.isQuerySelectorAdded() is false then variable = null 89 | * 90 | * In case, variable is null, we'll get a NullPointerException from Map.of(document, variable). 91 | * 92 | * Getting a NullPointerException here is normal because the query selector is not added. 93 | * In order to handle this situation, 94 | * we do nothing inside this catch but based on this null value, 95 | * we'll change the flow of this program inside the visiting methods. 96 | * */ 97 | try { 98 | variables = new HashMap(Map.of(document, variable)); 99 | } catch (NullPointerException e) { 100 | 101 | /* 102 | * We do nothing in case the query selector is not added 103 | * */ 104 | 105 | } 106 | 107 | final var keyword = resolveDeclarationKeyWord(configuration.getVariableDeclaration()); 108 | 109 | final var lineSeparator = System.lineSeparator(); 110 | 111 | if (configuration.isQuerySelectorAdded()) { 112 | writer.write("%s %s = document.querySelector(`%s`);%s%s".formatted(keyword, variable, selector,lineSeparator,lineSeparator)); 113 | } 114 | 115 | visit(writer, document.childNodes(), configuration, variables); 116 | writer.flush(); 117 | } 118 | 119 | private void visit(Writer writer, List nodes, Configuration configuration, Map variables) throws IOException { 120 | for (final Node node : nodes) { 121 | if (node instanceof Element) visit(writer, (Element) node, configuration, variables); 122 | else if (node instanceof Comment) { 123 | if (configuration.isCommentConversionModeActivated()) { 124 | visit(writer, (Comment) node, configuration, variables); 125 | } 126 | } else if (node instanceof TextNode) visit(writer, (TextNode) node, configuration, variables); 127 | } 128 | } 129 | 130 | private void visit(Writer writer, String parent, Attributes attributes) throws IOException { 131 | for (final Attribute attribute : attributes) { 132 | // FIXME: Should we special handle aria-* and data-* attributes ? 133 | // NOTE: Account for boolean attributes like open, required, disabled, contenteditable... 134 | final var value = attribute.hasDeclaredValue() ? attribute.getValue() 135 | : BOOLEAN_ATTRIBUTES.contains(attribute.getKey()) ? "true" 136 | : ""; 137 | writer.write(format("%s.setAttribute(`%s`, `%s`);\r\n", parent, attribute.getKey(), value)); 138 | } 139 | } 140 | 141 | private void visit(Writer writer, Comment comment, Configuration configuration, Map variables) throws IOException { 142 | final var variableNameStrategy = configuration.getVariableNameStrategy(); 143 | final var variable = variableNameStrategy.nextName("comment"); 144 | final var ancestor = resolveClosestNonSelfClosingAncestor(comment); 145 | String declarationKeyWord = resolveDeclarationKeyWord(configuration.getVariableDeclaration()); 146 | 147 | writer.write(format("\r\n%s %s = document.createComment(`%s`);\r\n", declarationKeyWord, variable, comment.getData())); 148 | 149 | 150 | /* 151 | * Based on this ternary operation on convert method, 152 | * 153 | * final var variable = configuration.isQuerySelectorAdded() 154 | ? variableNameStrategy.nextName("targetElement"):null; 155 | 156 | * if configuration.isQuerySelectorAdded() is true then variables.get(ancestor) is not null 157 | * if configuration.isQuerySelectorAdded() is false then variables.get(ancestor) is null 158 | * 159 | * In order to not appendChild to a null element (configuration.isQuerySelectorAdded() is false), we use this condition 160 | * */ 161 | 162 | if (variables.get(ancestor) != null) { 163 | writer.write(format("%s.appendChild(%s);\r\n", variables.get(ancestor), variable)); 164 | } 165 | } 166 | 167 | private void visit(Writer writer, TextNode textNode, Configuration configuration, Map variables) throws IOException { 168 | final var variableNameStrategy = configuration.getVariableNameStrategy(); 169 | final var variable = variableNameStrategy.nextName("text"); 170 | final var ancestor = resolveClosestNonSelfClosingAncestor(textNode); 171 | String declarationKeyWord = resolveDeclarationKeyWord(configuration.getVariableDeclaration()); 172 | 173 | writer.write(format("%s %s = document.createTextNode(`%s`);\r\n", declarationKeyWord, variable, textNode.getWholeText())); 174 | 175 | /* 176 | * Based on this ternary operation on convert method, 177 | * 178 | * final var variable = configuration.isQuerySelectorAdded() 179 | ? variableNameStrategy.nextName("targetElement"):null; 180 | 181 | * if configuration.isQuerySelectorAdded() is true then variables.get(ancestor) is not null 182 | * if configuration.isQuerySelectorAdded() is false then variables.get(ancestor) is null 183 | * 184 | * In order to not appendChild to a null element (configuration.isQuerySelectorAdded() is false), we use this condition 185 | * */ 186 | 187 | if (variables.get(ancestor) != null) { 188 | 189 | writer.write(format("%s.appendChild(%s);\r\n", variables.get(ancestor), variable)); 190 | 191 | } 192 | 193 | } 194 | 195 | private void visit(Writer writer, Element element, Configuration configuration, Map variables) throws IOException { 196 | final var declarationKeyWord = resolveDeclarationKeyWord(configuration.getVariableDeclaration()); 197 | final var variableNameStrategy = configuration.getVariableNameStrategy(); 198 | final var variable = variableNameStrategy.nextName(element.tagName()); 199 | 200 | variables.put(element, variable); 201 | writer.write(format("\r\n%s %s = document.createElement('%s');\r\n", declarationKeyWord, variable, element.tagName())); 202 | visit(writer, variable, element.attributes()); 203 | 204 | if ("script".equalsIgnoreCase(element.tagName())) { 205 | visitScriptNode(writer, element, variable, configuration, variables); 206 | } else { 207 | final var ancestor = resolveClosestNonSelfClosingAncestor(element); 208 | 209 | if (element.tag().isSelfClosing()) { 210 | // NOTE: JSoup wrongly considers the current element being visited as capable of having children. 211 | // This condition ensures that we append the self-closing element to its parent, 212 | // before processing its siblings, which JSoup parses as its children. 213 | 214 | /* 215 | * Based on this ternary operation on convert method, 216 | * 217 | * final var variable = configuration.isQuerySelectorAdded() 218 | ? variableNameStrategy.nextName("targetElement"):null; 219 | 220 | * if configuration.isQuerySelectorAdded() is true then variables.get(ancestor) is not null 221 | * if configuration.isQuerySelectorAdded() is false then variables.get(ancestor) is null 222 | * 223 | * In order to not appendChild to a null element (configuration.isQuerySelectorAdded() is false), we use this condition 224 | * */ 225 | 226 | 227 | if (variables.get(ancestor) != null) { 228 | 229 | writer.write(format("%s.appendChild(%s);\r\n", variables.get(ancestor), variable)); 230 | 231 | } 232 | 233 | visit(writer, element.childNodes(), configuration, variables); 234 | } else { 235 | visit(writer, element.childNodes(), configuration, variables); 236 | 237 | 238 | /* 239 | * Based on this ternary operation on convert method, 240 | * 241 | * final var variable = configuration.isQuerySelectorAdded() 242 | ? variableNameStrategy.nextName("targetElement"):null; 243 | 244 | * if configuration.isQuerySelectorAdded() is true then variables.get(ancestor) is not null 245 | * if configuration.isQuerySelectorAdded() is false then variables.get(ancestor) is null 246 | * 247 | * In order to not appendChild to a null element (configuration.isQuerySelectorAdded() is false), we use this condition 248 | * */ 249 | 250 | if (variables.get(ancestor) != null) { 251 | 252 | writer.write(format("%s.appendChild(%s);\r\n", variables.get(ancestor), variable)); 253 | 254 | } 255 | } 256 | } 257 | 258 | } 259 | 260 | private void visitScriptNode(Writer writer, Element element, String variable, 261 | Configuration configuration, Map variables) throws IOException { 262 | final var variableNameStrategy = configuration.getVariableNameStrategy(); 263 | final var ancestor = resolveClosestNonSelfClosingAncestor(element); 264 | 265 | if (element.attr(element.absUrl("type")).isBlank()) { 266 | writer.write(format("\r\n%s.type = `text/javascript`;\r\n", variable)); 267 | } 268 | 269 | String declarationKeyWord = resolveDeclarationKeyWord(configuration.getVariableDeclaration()); 270 | final var script = ((TextNode) element.childNodes().get(0)).getWholeText() 271 | .replaceAll("`", "\\\\`"); 272 | // FIXME: Will be quirky without tokenizing script code but then, it doesn't matter as it could be 273 | // TypeScript or Mustache template or, Pig, etc. We may consider tokenizing those languages 274 | final var scriptTextVariable = variableNameStrategy.nextName("text"); 275 | 276 | /* 277 | * Based on this ternary operation on convert method, 278 | * 279 | * final var variable = configuration.isQuerySelectorAdded() 280 | ? variableNameStrategy.nextName("targetElement"):null; 281 | 282 | * if configuration.isQuerySelectorAdded() is true then variables.get(ancestor) is not null 283 | * if configuration.isQuerySelectorAdded() is false then variables.get(ancestor) is null 284 | * 285 | * In order to not appendChild to a null element (configuration.isQuerySelectorAdded() is false), we use this condition 286 | * */ 287 | 288 | 289 | if (variables.get(ancestor) != null) { 290 | 291 | 292 | writer.write(format("\r\n" + join("\r\n", "try {", 293 | " %6$s %3$s = document.createTextNode(`%1$s`);", 294 | " %2$s.appendChild(%3$s);", 295 | " %4$s.appendChild(%2$s);", 296 | "} catch (_) {", 297 | " %2$s.text = `%1$s`;", 298 | " %4$s.appendChild(%2$s);", 299 | "}") + "\r\n", 300 | script, variable, scriptTextVariable, variables.get(ancestor), variable, declarationKeyWord)); 301 | 302 | 303 | } else { 304 | 305 | writer.write(format("\r\n" + join("\r\n", "try {", 306 | " %4$s %3$s = document.createTextNode(`%1$s`);", 307 | " %2$s.appendChild(%3$s);", 308 | "} catch (_) {", 309 | " %2$s.text = `%1$s`;", 310 | "}") + "\r\n", 311 | script, variable, scriptTextVariable, declarationKeyWord)); 312 | 313 | } 314 | } 315 | 316 | private String resolveDeclarationKeyWord(VariableDeclaration variableDeclaration) { 317 | return variableDeclaration.name().toLowerCase(); 318 | } 319 | } -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/internal/InlineOutputStreamResolver.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.core.internal; 2 | 3 | import com.osscameroon.jsgenerator.core.OutputStreamResolver; 4 | import org.springframework.lang.NonNull; 5 | 6 | import java.util.Map; 7 | 8 | import static java.lang.String.format; 9 | import static java.lang.String.valueOf; 10 | 11 | public class InlineOutputStreamResolver implements OutputStreamResolver { 12 | @Override 13 | public String resolve(@NonNull String template, @NonNull Map container) { 14 | return template 15 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", EXTENSION), valueOf(container.get(EXTENSION))) 16 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", INDEX), valueOf(container.get(INDEX))); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/internal/PathOutputStreamResolver.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.core.internal; 2 | 3 | import com.osscameroon.jsgenerator.core.OutputStreamResolver; 4 | import org.springframework.lang.NonNull; 5 | 6 | import java.util.Map; 7 | 8 | import static java.lang.String.format; 9 | import static java.lang.String.valueOf; 10 | 11 | public class PathOutputStreamResolver implements OutputStreamResolver { 12 | @Override 13 | public String resolve(@NonNull String template, @NonNull Map container) { 14 | return template 15 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", ORIGINAL_DIRECTORY), valueOf(container.get(ORIGINAL_DIRECTORY))) 16 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", ORIGINAL_EXTENSION), valueOf(container.get(ORIGINAL_EXTENSION))) 17 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", ORIGINAL_BASENAME), valueOf(container.get(ORIGINAL_BASENAME))) 18 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", EXTENSION), valueOf(container.get(EXTENSION))) 19 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", ORIGINAL), valueOf(container.get(ORIGINAL))) 20 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", INDEX), valueOf(container.get(INDEX))); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/internal/RandomVariableNameStrategy.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.core.internal; 2 | 3 | import com.osscameroon.jsgenerator.core.VariableNameStrategy; 4 | import org.springframework.lang.NonNull; 5 | 6 | import static java.util.UUID.randomUUID; 7 | 8 | public class RandomVariableNameStrategy implements VariableNameStrategy { 9 | @Override 10 | public String nextName(@NonNull String type) { 11 | return "_" + randomUUID().toString().replaceAll("[^a-zA-Z0-9]", "_"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/internal/StdinOutputStreamResolver.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.core.internal; 2 | 3 | import com.osscameroon.jsgenerator.core.OutputStreamResolver; 4 | import org.springframework.lang.NonNull; 5 | 6 | import java.util.Map; 7 | 8 | import static java.lang.String.format; 9 | import static java.lang.String.valueOf; 10 | 11 | public class StdinOutputStreamResolver implements OutputStreamResolver { 12 | @Override 13 | public String resolve(@NonNull String template, @NonNull Map container) { 14 | return template 15 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", EXTENSION), valueOf(container.get(EXTENSION))); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/internal/TypeBasedVariableNameStrategy.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.core.internal; 2 | 3 | import com.osscameroon.jsgenerator.core.VariableNameStrategy; 4 | import org.springframework.lang.NonNull; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.concurrent.atomic.AtomicLong; 9 | 10 | import static java.lang.String.format; 11 | 12 | public class TypeBasedVariableNameStrategy implements VariableNameStrategy { 13 | private final Map counters = new HashMap<>(); 14 | 15 | @Override 16 | public String nextName(@NonNull String type) { 17 | // NOTE: issue#145 careful with custom element about casing and dash, not to translate in JavaScript identifiers 18 | var identifier = type; 19 | final var HAS_DASH = type.contains("-"); 20 | final var IS_ROOT = "targetElement".equals(type); 21 | final var HAS_UPPER_CASE = !type.chars() 22 | .allMatch(character -> Character.toLowerCase(character) == character); 23 | 24 | if (!IS_ROOT) { 25 | identifier = HAS_UPPER_CASE ? type.toLowerCase() : identifier; 26 | identifier = HAS_DASH 27 | ? type.replaceAll("-", "_").replaceAll("_+", "_") : identifier; 28 | 29 | if (HAS_DASH || HAS_UPPER_CASE) { 30 | identifier = "custom_%s".formatted(identifier); 31 | } 32 | } 33 | 34 | return format("%s_%03d", identifier, counters.computeIfAbsent(type, __ -> new AtomicLong(1)).getAndIncrement()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.osscameroon.jsgenerator.core { 2 | exports com.osscameroon.jsgenerator.core; 3 | exports com.osscameroon.jsgenerator.core.autoconfigure; 4 | 5 | opens com.osscameroon.jsgenerator.core.autoconfigure; 6 | 7 | requires org.jsoup; 8 | requires spring.boot; 9 | requires spring.context; 10 | 11 | requires spring.boot.autoconfigure; 12 | requires spring.core; 13 | } 14 | -------------------------------------------------------------------------------- /jsgenerator-core/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.osscameroon.jsgenerator.core.autoconfigure.JsGeneratorCoreAutoconfigure -------------------------------------------------------------------------------- /jsgenerator-desktop/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /jsgenerator-desktop/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | jsgenerator 8 | com.osscameroon 9 | ${revision} 10 | 11 | 12 | 4.0.0 13 | jsgenerator-desktop 14 | jsgenerator-desktop 15 | 16 | 17 | com.osscameroon.jsgenerator.desktop.autoconfigure.JsGeneratorDesktop 18 | 19 | 20 | 21 | 22 | com.osscameroon 23 | jsgenerator-core 24 | 25 | 26 | org.openjfx 27 | javafx-fxml 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.graalvm.buildtools 35 | native-maven-plugin 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-maven-plugin 40 | 41 | false 42 | 43 | 44 | 45 | org.apache.maven.plugins 46 | maven-compiler-plugin 47 | 48 | 22 49 | 22 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /jsgenerator-desktop/src/main/java/com/osscameroon/jsgenerator/desktop/autoconfigure/JsGeneratorDesktop.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.desktop.autoconfigure; 2 | 3 | import com.osscameroon.jsgenerator.core.autoconfigure.JsGeneratorCoreAutoconfigure; 4 | import com.osscameroon.jsgenerator.desktop.controller.FxmlNavigator; 5 | import com.osscameroon.jsgenerator.desktop.controller.FxmlResolver; 6 | import com.osscameroon.jsgenerator.desktop.controller.HelloViewController; 7 | import javafx.application.Application; 8 | import javafx.fxml.FXMLLoader; 9 | import javafx.scene.Parent; 10 | import javafx.scene.Scene; 11 | import javafx.stage.Stage; 12 | import org.springframework.boot.SpringApplication; 13 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 14 | import org.springframework.boot.autoconfigure.SpringBootApplication; 15 | import org.springframework.context.ApplicationContext; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Lazy; 18 | import org.springframework.core.io.ClassPathResource; 19 | 20 | import java.io.IOException; 21 | 22 | @ImportAutoConfiguration(JsGeneratorCoreAutoconfigure.class) 23 | @SpringBootApplication(scanBasePackageClasses = HelloViewController.class) 24 | public class JsGeneratorDesktop extends Application { 25 | private static ApplicationContext context; 26 | private static FxmlResolver fxmlResolver; 27 | private static Scene scene; 28 | 29 | public static void main(String[] args) { 30 | context = SpringApplication.run(JsGeneratorDesktop.class, args); 31 | fxmlResolver = context.getBean(FxmlResolver.class); 32 | launch(JsGeneratorDesktop.class, args); 33 | } 34 | 35 | /** 36 | * Inject this bean to navigate from one view to another, like a router. 37 | * 38 | * @return 39 | */ 40 | @Bean 41 | @Lazy 42 | public FxmlNavigator fxmlNavigator() { 43 | return scene::setRoot; 44 | } 45 | 46 | @Bean 47 | public FxmlResolver fxmlResolver() { 48 | return path -> { 49 | path = "com/osscameroon/jsgenerator/desktop/controller/%s.fxml".formatted(path); 50 | final var loader = new FXMLLoader(new ClassPathResource(path).getURL()); 51 | loader.setControllerFactory(context::getBean); 52 | return (Parent) loader.load(); 53 | }; 54 | } 55 | 56 | @Override 57 | public void start(Stage stage) throws IOException { 58 | final var parent = fxmlResolver.resolve("hello-view"); 59 | stage.setScene(scene = new Scene(parent)); 60 | stage.show(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /jsgenerator-desktop/src/main/java/com/osscameroon/jsgenerator/desktop/controller/FxmlNavigator.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.desktop.controller; 2 | 3 | import javafx.scene.Parent; 4 | 5 | @FunctionalInterface 6 | public interface FxmlNavigator { 7 | void navigate(Parent parent); 8 | } 9 | -------------------------------------------------------------------------------- /jsgenerator-desktop/src/main/java/com/osscameroon/jsgenerator/desktop/controller/FxmlResolver.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.desktop.controller; 2 | 3 | import javafx.scene.Parent; 4 | 5 | import java.io.IOException; 6 | 7 | @FunctionalInterface 8 | public interface FxmlResolver { 9 | Parent resolve(String relativePathWithoutExtension) throws IOException; 10 | } 11 | -------------------------------------------------------------------------------- /jsgenerator-desktop/src/main/java/com/osscameroon/jsgenerator/desktop/controller/HelloViewController.java: -------------------------------------------------------------------------------- 1 | package com.osscameroon.jsgenerator.desktop.controller; 2 | 3 | import com.osscameroon.jsgenerator.core.Converter; 4 | import javafx.fxml.FXML; 5 | import javafx.scene.control.Label; 6 | import javafx.scene.control.TextArea; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.io.ByteArrayInputStream; 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.IOException; 12 | import java.nio.charset.StandardCharsets; 13 | 14 | @Component 15 | public final class HelloViewController { 16 | private final Converter converter; 17 | 18 | @FXML 19 | private TextArea inputArea; 20 | @FXML 21 | private Label outputLabel; 22 | 23 | public HelloViewController(Converter converter) { 24 | this.converter = converter; 25 | } 26 | 27 | @FXML 28 | private void convert() throws IOException { 29 | try (var stream = new ByteArrayOutputStream()) { 30 | converter.convert(new ByteArrayInputStream(inputArea.textProperty().getValue().getBytes()), stream); 31 | outputLabel.setText(stream.toString(StandardCharsets.UTF_8)); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /jsgenerator-desktop/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.osscameroon.jsgenerator.desktop { 2 | exports com.osscameroon.jsgenerator.desktop.autoconfigure; 3 | exports com.osscameroon.jsgenerator.desktop.controller; 4 | 5 | opens com.osscameroon.jsgenerator.desktop.controller to javafx.fxml, spring.beans; 6 | opens com.osscameroon.jsgenerator.desktop.autoconfigure to javafx.fxml, spring.beans; 7 | 8 | requires com.osscameroon.jsgenerator.core; 9 | 10 | requires spring.boot.autoconfigure; 11 | requires spring.boot; 12 | requires spring.context; 13 | 14 | requires javafx.graphics; 15 | requires javafx.controls; 16 | requires javafx.fxml; 17 | requires spring.core; 18 | } 19 | -------------------------------------------------------------------------------- /jsgenerator-desktop/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | banner: 3 | location: classpath:/banner.txt 4 | -------------------------------------------------------------------------------- /jsgenerator-desktop/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | _ _____ _____ ______ _ _ ______ _____ _______ ____ _____ _____ ______ _____ _ _________ ____ _____ 3 | | |/ ____|/ ____| ____| \ | | ____| __ \ /\|__ __/ __ \| __ \ | __ \| ____|/ ____| |/ /__ __/ __ \| __ \ 4 | | | (___ | | __| |__ | \| | |__ | |__) | / \ | | | | | | |__) |_____| | | | |__ | (___ | ' / | | | | | | |__) | 5 | _ | |\___ \| | |_ | __| | . ` | __| | _ / / /\ \ | | | | | | _ /______| | | | __| \___ \| < | | | | | | ___/ 6 | | |__| |____) | |__| | |____| |\ | |____| | \ \ / ____ \| | | |__| | | \ \ | |__| | |____ ____) | . \ | | | |__| | | 7 | \____/|_____/ \_____|______|_| \_|______|_| \_\/_/ \_\_| \____/|_| \_\ |_____/|______|_____/|_|\_\ |_| \____/|_| 8 | 9 | 10 | -------------------------------------------------------------------------------- /jsgenerator-desktop/src/main/resources/com/osscameroon/jsgenerator/desktop/controller/hello-view.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 |