├── .github ├── FUNDING.yml └── workflows │ └── jekyll-gh-pages.yml ├── .gitignore ├── LICENSE ├── Readme.md ├── details.html ├── htmlflow.uml ├── pom.xml └── src ├── main ├── java │ └── htmlflow │ │ ├── HtmlDoc.java │ │ ├── HtmlFlow.java │ │ ├── HtmlPage.java │ │ ├── HtmlTemplate.java │ │ ├── HtmlView.java │ │ ├── HtmlViewAsync.java │ │ ├── continuations │ │ ├── HtmlContinuation.java │ │ ├── HtmlContinuationAsync.java │ │ ├── HtmlContinuationAsyncTerminationNode.java │ │ ├── HtmlContinuationSync.java │ │ ├── HtmlContinuationSyncCloseAndIndent.java │ │ ├── HtmlContinuationSyncDynamic.java │ │ └── HtmlContinuationSyncStatic.java │ │ ├── exceptions │ │ └── HtmlFlowAppendException.java │ │ ├── flowifier │ │ ├── AbstractHtmlToJavaHtmlFlowNodeVisitor.java │ │ ├── DefaultHtmlToJavaHtmlFlowNodeVisitor.java │ │ ├── Flowifier.java │ │ └── HtmlToJavaHtmlFlowNodeVisitor.java │ │ └── visitor │ │ ├── HtmlDocVisitor.java │ │ ├── HtmlViewVisitor.java │ │ ├── HtmlViewVisitorAsync.java │ │ ├── HtmlVisitor.java │ │ ├── Indentation.java │ │ ├── PreprocessingVisitor.java │ │ ├── PreprocessingVisitorAsync.java │ │ └── Tags.java ├── kotlin │ └── htmlflow │ │ ├── HtmlFlowExtensions.kt │ │ ├── HtmlFlowSuspending.kt │ │ ├── HtmlViewSuspend.kt │ │ ├── HtmlViewVisitorSuspend.kt │ │ ├── PreprocessingVisitorSuspend.kt │ │ └── continuations │ │ ├── HtmlContinuationSuspendable.kt │ │ ├── HtmlContinuationSuspendableAsync.kt │ │ ├── HtmlContinuationSuspendableSync.kt │ │ ├── HtmlContinuationSuspendableSyncCloseAndIndent.kt │ │ ├── HtmlContinuationSuspendableSyncDynamic.kt │ │ ├── HtmlContinuationSuspendableSyncStatic.kt │ │ ├── HtmlContinuationSuspendableTerminationNode.kt │ │ └── HtmlContinuationSuspending.kt └── resources │ └── templates │ └── HtmlView-Header.txt └── test ├── java └── htmlflow │ ├── exceptions │ └── HtmlFlowAppendExceptionTest.java │ ├── flowifier │ └── test │ │ └── TestFlowifier.java │ ├── test │ ├── TestAsyncView.java │ ├── TestAsyncViewInConcurInMultpleThreads.java │ ├── TestAsyncViewInConcurrency.java │ ├── TestAttributesClassId.java │ ├── TestAwaitDynamic.java │ ├── TestBooleanAttributes.java │ ├── TestCustomElements.java │ ├── TestDivDetails.java │ ├── TestDivDetailsInConcurrency.java │ ├── TestDynamicCases.java │ ├── TestDynamicVersusOf.java │ ├── TestDynamicVersusOfWithPartials.java │ ├── TestEscapeText.java │ ├── TestFormAttributes.java │ ├── TestHtmlViewAsElement.java │ ├── TestIndentation.java │ ├── TestNewScriptTypes.java │ ├── TestPartialsToPrintStream.java │ ├── TestReactiveView.java │ ├── TestResourceNotFound.java │ ├── TestTable.java │ ├── TestVoidElements.java │ ├── TestWrongUseOfViews.java │ ├── Utils.java │ ├── model │ │ ├── AsyncDynamicModel.java │ │ ├── AsyncModel.java │ │ ├── Priority.java │ │ ├── Status.java │ │ ├── Stock.java │ │ ├── Student.java │ │ ├── StudentWithGrade.java │ │ ├── Task.java │ │ ├── TaskDelayed.java │ │ └── Track.java │ └── views │ │ ├── HtmlCustomElements.java │ │ ├── HtmlDynamic.java │ │ ├── HtmlDynamicChainTwiceOnTopgenius.java │ │ ├── HtmlDynamicStocks.java │ │ ├── HtmlForReadme.java │ │ ├── HtmlLists.java │ │ ├── HtmlPartials.java │ │ ├── HtmlTables.java │ │ ├── HtmlVoidElements.java │ │ └── HtmlWithoutIndentation.java │ └── visitor │ └── TagsTest.java ├── kotlin └── htmlflow │ └── test │ ├── TestKotlinExtensions.kt │ ├── TestKotlinExtensionsOnPartials.kt │ ├── TestKotlinSuspend.kt │ ├── TestSuspendableView.kt │ ├── TestSuspendableViewArtist.kt │ ├── TestSuspendableViewInConcurInMultpleThreads.kt │ ├── TestSuspendableViewInConcurrency.kt │ └── TestSuspendableViewWeather.kt └── resources ├── TaskList.html ├── asyncTest.html ├── asyncTestSecond.html ├── asyncTestWithDynamic.html ├── customElements.html ├── htmlWithoutIndentationBodyDivP.html ├── htmlWithoutIndentationBodyPre.html ├── htmlflowSample05ForFlowifier.java ├── logging.properties ├── nestedTable.html ├── partialViewHeader.html ├── stocks3items.html ├── stocks3others.html ├── stocks5items.html ├── task3.html ├── task4.html ├── task5.html ├── task9.html ├── testAddPartialAndCheckParentPrintStream.html ├── testIdAndClassAttribute.html ├── topgeniusBadAndSit.html ├── topgeniusSpaceAndPressure.html └── voidElements.html /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: fmcarvalho 4 | patreon: # Replace with a single Patreon username 5 | open_collective: htmlflow 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/jekyll-gh-pages.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 2 | name: Deploy Jekyll with GitHub Pages dependencies preinstalled 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["gh-pages"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Build job 26 | build: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | with: 32 | ref: gh-pages 33 | - name: Setup Pages 34 | uses: actions/configure-pages@v3 35 | - name: Build with Jekyll 36 | uses: actions/jekyll-build-pages@v1 37 | with: 38 | source: ./ 39 | destination: ./_site 40 | - name: Upload artifact 41 | uses: actions/upload-pages-artifact@v2 42 | 43 | # Deployment job 44 | deploy: 45 | environment: 46 | name: github-pages 47 | url: ${{ steps.deployment.outputs.page_url }} 48 | runs-on: ubuntu-latest 49 | needs: build 50 | steps: 51 | - name: Deploy to GitHub Pages 52 | id: deployment 53 | uses: actions/deploy-pages@v2 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | /bin/ 3 | 4 | # InteliJ 5 | .idea 6 | *.iml 7 | 8 | # Eclipse Core 9 | .project 10 | 11 | # JDT-specific (Eclipse Java Development Tools) 12 | .classpath -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Miguel Gamboa (gamboa.pt) 4 | Copyright (c) Luís Duarte (https://github.com/lcduarte) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /details.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 | Typesafe is awesome! :-) 6 |

7 | 8 | -------------------------------------------------------------------------------- /htmlflow.uml: -------------------------------------------------------------------------------- 1 | 2 | 3 | JAVA 4 | htmlflow 5 | 6 | org.xmlet.htmlapifaster.Element 7 | htmlflow.HtmlView 8 | htmlflow.HtmlVisitorPrintStream 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Methods 21 | 22 | All 23 | private 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/HtmlDoc.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow; 26 | 27 | import htmlflow.visitor.HtmlDocVisitor; 28 | import org.xmlet.htmlapifaster.Html; 29 | 30 | /** 31 | * Static Html view. 32 | * 33 | * @author Miguel Gamboa, Luís Duare 34 | */ 35 | public class HtmlDoc extends HtmlPage { 36 | 37 | private final HtmlDocVisitor visitor; 38 | 39 | HtmlDoc(HtmlDocVisitor visitor) { 40 | this.visitor = visitor; 41 | } 42 | 43 | public final Html html() { 44 | this.getVisitor().write(HEADER); 45 | return new Html<>(this); 46 | } 47 | 48 | @Override 49 | public String getName() { 50 | return "HtmlDoc"; 51 | } 52 | 53 | @Override 54 | public HtmlPage setIndented(boolean isIndented) { 55 | return new HtmlDoc(getVisitor().clone(isIndented)); 56 | } 57 | 58 | @Override 59 | public HtmlPage threadSafe() { 60 | throw new IllegalStateException("HtmlDoc is not reusable and does not keep internal static blocks!" + 61 | "Thus it does not require thread safety!"); 62 | } 63 | 64 | @Override 65 | public HtmlDocVisitor getVisitor() { 66 | return visitor; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/HtmlPage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow; 26 | 27 | import org.xmlet.htmlapifaster.Div; 28 | import org.xmlet.htmlapifaster.Element; 29 | import org.xmlet.htmlapifaster.Html; 30 | import org.xmlet.htmlapifaster.Tr; 31 | 32 | import java.io.BufferedReader; 33 | import java.io.FileNotFoundException; 34 | import java.io.IOException; 35 | import java.io.InputStream; 36 | import java.io.InputStreamReader; 37 | import java.io.UncheckedIOException; 38 | import java.net.URL; 39 | 40 | import static java.util.stream.Collectors.joining; 41 | 42 | /** 43 | * The root container for HTML elements. 44 | * It is responsible for managing the {@code org.xmlet.htmlapi.ElementVisitor} 45 | * implementation, which is responsible for printing the tree of elements and 46 | * attributes. 47 | * 48 | * Instances of HtmlPage are immutable. Any change to its properties returns a new 49 | * instance of HtmlPage. 50 | * 51 | * @author Miguel Gamboa, Luís Duare 52 | * created on 29-03-2012 53 | */ 54 | public abstract class HtmlPage implements Element> { 55 | 56 | public static final String HEADER; 57 | private static final String NEWLINE = System.getProperty("line.separator"); 58 | private static final String HEADER_TEMPLATE = "templates/HtmlView-Header.txt"; 59 | 60 | static { 61 | try { 62 | URL headerUrl = HtmlPage.class 63 | .getClassLoader() 64 | .getResource(HEADER_TEMPLATE); 65 | if(headerUrl == null) 66 | throw new FileNotFoundException(HEADER_TEMPLATE); 67 | InputStream headerStream = headerUrl.openStream(); 68 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(headerStream))) { 69 | HEADER = reader.lines().collect(joining(NEWLINE)); 70 | } 71 | } catch (IOException e) { 72 | throw new UncheckedIOException(e); 73 | } 74 | } 75 | 76 | public abstract Html html(); 77 | 78 | public final Div div() { 79 | return new Div<>(this); 80 | } 81 | 82 | public final Tr tr() { 83 | return new Tr<>(this); 84 | } 85 | 86 | /** 87 | * Returns a new instance of HtmlFlow with the same properties of this object 88 | * but with indented set to the value of isIndented parameter. 89 | */ 90 | public abstract HtmlPage setIndented(boolean isIndented); 91 | 92 | @Override 93 | public final HtmlPage self() { 94 | return this; 95 | } 96 | 97 | public abstract HtmlPage threadSafe(); 98 | 99 | @Override 100 | public Element __() { 101 | throw new IllegalStateException(getName() + " is the root of Html tree and it has not any parent."); 102 | } 103 | 104 | @Override 105 | public Element getParent() { 106 | throw new IllegalStateException(getName() + " is the root of Html tree and it has not any parent."); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/HtmlTemplate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow; 26 | 27 | /** 28 | * The model of type T is obtained from view's render() method 29 | * and passed to dynamic blocks. 30 | */ 31 | @FunctionalInterface 32 | public interface HtmlTemplate { 33 | 34 | void resolve(HtmlPage page); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/HtmlView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow; 26 | 27 | import htmlflow.visitor.HtmlVisitor; 28 | import org.xmlet.htmlapifaster.Html; 29 | 30 | import java.util.function.Supplier; 31 | 32 | /** 33 | * Dynamic views can be bound to a Model object. 34 | * 35 | * @param Type of the model rendered with this view. 36 | * 37 | * @author Miguel Gamboa, Luís Duare 38 | */ 39 | public class HtmlView extends HtmlPage { 40 | /** 41 | * Function that consumes an HtmlView to produce HTML elements. 42 | */ 43 | private final HtmlTemplate template; 44 | /** 45 | * This field is like a union with the threadLocalVisitor, being used alternatively. 46 | * For non thread safe scenarios Visitors maybe shared concurrently by multiple threads. 47 | * On the other-hand, in thread-safe scenarios each thread must have its own visitor to 48 | * emit HTML to the output, and we use the threadLocalVisitor field instead. 49 | */ 50 | private final HtmlVisitor visitor; 51 | /** 52 | * This issue is regarding ThreadLocal variables that are supposed to be garbage collected. 53 | * The given example deals with a static field of ThreadLocal which persists beyond an instance. 54 | * In this case the ThreadLocal is hold in an instance field and should stay with all 55 | * thread local instances during its entire life cycle. 56 | */ 57 | @java.lang.SuppressWarnings("squid:S5164") 58 | private final ThreadLocal threadLocalVisitor; 59 | protected final Supplier visitorSupplier; 60 | private final boolean threadSafe; 61 | /** 62 | * Auxiliary constructor used by clone(). 63 | */ 64 | HtmlView( 65 | Supplier visitorSupplier, 66 | HtmlTemplate template, 67 | boolean threadSafe) 68 | { 69 | this.visitorSupplier = visitorSupplier; 70 | this.template = template; 71 | this.threadSafe = threadSafe; 72 | if(threadSafe) { 73 | this.visitor = null; 74 | this.threadLocalVisitor = ThreadLocal.withInitial(visitorSupplier); 75 | } else { 76 | this.visitor = visitorSupplier.get(); 77 | this.threadLocalVisitor = null; 78 | } 79 | } 80 | 81 | public final Html html() { 82 | this.getVisitor().write(HEADER); 83 | return new Html<>(this); 84 | } 85 | 86 | public HtmlView threadSafe(){ 87 | return clone(visitorSupplier, true); 88 | } 89 | 90 | @Override 91 | public HtmlVisitor getVisitor() { 92 | return threadSafe 93 | ? threadLocalVisitor.get() 94 | : visitor; 95 | } 96 | 97 | public HtmlView setOut(Appendable out) { 98 | getVisitor().setAppendable(out); 99 | return this; 100 | } 101 | 102 | @Override 103 | public String getName() { 104 | return "HtmlView"; 105 | } 106 | 107 | public String render() { 108 | return render(null); 109 | } 110 | 111 | public String render(M model) { 112 | StringBuilder str = ((StringBuilder) getVisitor().out()); 113 | str.setLength(0); 114 | getVisitor().resolve(model); 115 | return str.toString(); 116 | } 117 | 118 | public void write(M model) { 119 | getVisitor().resolve(model); 120 | } 121 | 122 | public void write() { 123 | write(null); 124 | } 125 | 126 | /** 127 | * Since HtmlView is immutable this is the preferred way to create a copy of the 128 | * existing HtmlView instance with a different threadSafe state. 129 | * 130 | * @param visitorSupplier 131 | * @param threadSafe 132 | */ 133 | protected final HtmlView clone( 134 | Supplier visitorSupplier, 135 | boolean threadSafe) 136 | { 137 | return new HtmlView<>(visitorSupplier, template, threadSafe); 138 | } 139 | 140 | /** 141 | * Returns a new instance of HtmlFlow with the same properties of this object 142 | * but with indented set to the value of isIndented parameter. 143 | */ 144 | @Override 145 | public final HtmlView setIndented(boolean isIndented) { 146 | return HtmlFlow.view(getVisitor().out(), template, isIndented, threadSafe); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/HtmlViewAsync.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-22, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow; 26 | 27 | import htmlflow.visitor.HtmlViewVisitorAsync; 28 | import org.xmlet.htmlapifaster.Html; 29 | 30 | import java.util.concurrent.CompletableFuture; 31 | 32 | /** 33 | * Asynchronous views can be bound to a domain object with an asynchronous API. 34 | * 35 | * @param Type of the model rendered with this view. 36 | * 37 | * @author Pedro Fialho 38 | */ 39 | public class HtmlViewAsync extends HtmlPage { 40 | /** 41 | * Function that consumes an HtmlView to produce HTML elements. 42 | */ 43 | private final HtmlTemplate template; 44 | 45 | private final HtmlViewVisitorAsync visitor; 46 | 47 | private final boolean threadSafe; 48 | 49 | HtmlViewAsync(HtmlViewVisitorAsync visitor, HtmlTemplate template) { 50 | this(visitor, template, true); 51 | } 52 | 53 | public HtmlViewAsync(HtmlViewVisitorAsync visitor, HtmlTemplate template, boolean safe) { 54 | this.visitor = visitor; 55 | this.template = template; 56 | this.threadSafe = safe; 57 | } 58 | 59 | @Override 60 | public final Html html() { 61 | visitor.write(HEADER); 62 | return new Html<>(this); 63 | } 64 | 65 | @Override 66 | public HtmlViewAsync setIndented(boolean isIndented) { 67 | return HtmlFlow.viewAsync(template, isIndented, threadSafe); 68 | } 69 | 70 | @Override 71 | public HtmlViewVisitorAsync getVisitor() { 72 | return visitor; 73 | } 74 | 75 | @Override 76 | public String getName() { 77 | return "HtmlViewAsync"; 78 | } 79 | 80 | @Override 81 | public HtmlViewAsync threadSafe(){ 82 | return new HtmlViewAsync<>(visitor, template); 83 | } 84 | 85 | public HtmlViewAsync threadUnsafe(){ 86 | return new HtmlViewAsync<>(visitor, template, false); 87 | } 88 | 89 | 90 | public final CompletableFuture writeAsync(Appendable out, M model) { 91 | if (threadSafe) { 92 | return visitor.clone(out).finishedAsync(model); 93 | } 94 | visitor.setAppendable(out); 95 | return visitor.finishedAsync(model); 96 | } 97 | 98 | public final CompletableFuture renderAsync() { 99 | return renderAsync(null); 100 | } 101 | 102 | public final CompletableFuture renderAsync(M model) { 103 | StringBuilder str = new StringBuilder(); 104 | return writeAsync(str, model).thenApply( nothing -> str.toString()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/continuations/HtmlContinuation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-2022, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte) 5 | * and Pedro Fialho. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | package htmlflow.continuations; 26 | 27 | import htmlflow.visitor.HtmlVisitor; 28 | import org.xmlet.htmlapifaster.Element; 29 | import org.xmlet.htmlapifaster.ElementVisitor; 30 | 31 | import java.lang.reflect.Constructor; 32 | import java.lang.reflect.InvocationTargetException; 33 | 34 | /** 35 | * Base class for a linked list of nodes, corresponding to HtmlContinuation objects. 36 | * HtmlContinuation is responsible for emitting an HTML block and call the next node. 37 | */ 38 | public abstract class HtmlContinuation extends HtmlContinuationSuspendable { 39 | /** 40 | * A negative number means that should be ignored. 41 | */ 42 | final int currentDepth; 43 | /** 44 | * In case of the middle of a beginning tag, it tells if it is closed, or not, 45 | * i.e. if it is {@code ""} or it is {@code " E copyElement(E element, HtmlVisitor v) { 86 | try { 87 | Constructor ctor = ((Class) element 88 | .getClass()) 89 | .getDeclaredConstructor(Element.class, ElementVisitor.class, boolean.class); 90 | ctor.setAccessible(true); 91 | return ctor.newInstance(element.getParent(), v, false); // false to not dispatch visit now 92 | } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException ex) { 93 | throw new IllegalStateException(ex); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/continuations/HtmlContinuationAsync.java: -------------------------------------------------------------------------------- 1 | package htmlflow.continuations; 2 | 3 | import htmlflow.visitor.HtmlVisitor; 4 | import org.xmlet.htmlapifaster.Element; 5 | import org.xmlet.htmlapifaster.async.AwaitConsumer; 6 | 7 | /** 8 | * HtmlContinuation for an asynchronous block (i.e. AwaitConsumer) depending of an asynchronous object model. 9 | * The next continuation will be invoked on completion of asynchronous object model. 10 | * 11 | * @param the type of the parent HTML element received by the dynamic HTML block. 12 | * @param the type of the template's model. 13 | */ 14 | public class HtmlContinuationAsync extends HtmlContinuation { 15 | 16 | final E element; 17 | final AwaitConsumer consumer; 18 | 19 | public HtmlContinuationAsync(int currentDepth, 20 | boolean isClosed, 21 | E element, 22 | AwaitConsumer consumer, 23 | HtmlVisitor visitor, 24 | HtmlContinuation next) { 25 | super(currentDepth, isClosed, visitor, next); 26 | this.element = element; 27 | this.consumer = consumer; 28 | } 29 | 30 | @Override 31 | public final void execute(Object model) { 32 | if (currentDepth >= 0) { 33 | this.visitor.setIsClosed(isClosed); 34 | this.visitor.setDepth(currentDepth); 35 | } 36 | this.consumer.accept(element, (T) model, () -> { 37 | if (next != null) { 38 | next.execute(model); 39 | } 40 | }); 41 | } 42 | 43 | @Override 44 | public HtmlContinuation copy(HtmlVisitor v) { 45 | return new HtmlContinuationAsync<>( 46 | currentDepth, 47 | isClosed, 48 | copyElement(element, v), 49 | consumer, 50 | v, 51 | next != null ? next.copy(v) : null); // call copy recursively 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/continuations/HtmlContinuationAsyncTerminationNode.java: -------------------------------------------------------------------------------- 1 | package htmlflow.continuations; 2 | 3 | import htmlflow.visitor.HtmlVisitor; 4 | 5 | import java.util.concurrent.CompletableFuture; 6 | 7 | /** 8 | * @author Pedro Fialho 9 | **/ 10 | public class HtmlContinuationAsyncTerminationNode extends HtmlContinuation { 11 | 12 | private final CompletableFuture cf; 13 | 14 | public HtmlContinuationAsyncTerminationNode(CompletableFuture cf) { 15 | super(-1, false, null, null); 16 | this.cf = cf; 17 | } 18 | 19 | public void execute(Object model) { 20 | cf.complete(null); 21 | } 22 | 23 | @Override 24 | public HtmlContinuationAsyncTerminationNode copy(HtmlVisitor visitor) { 25 | throw new UnsupportedOperationException("Used once and never copied!"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/continuations/HtmlContinuationSync.java: -------------------------------------------------------------------------------- 1 | package htmlflow.continuations; 2 | 3 | import htmlflow.visitor.HtmlVisitor; 4 | 5 | public abstract class HtmlContinuationSync extends HtmlContinuation{ 6 | /** 7 | * @param currentDepth Indentation depth associated to this block. 8 | * @param isClosed 9 | * @param visitor 10 | * @param next 11 | */ 12 | protected HtmlContinuationSync(int currentDepth, boolean isClosed, HtmlVisitor visitor, HtmlContinuation next) { 13 | super(currentDepth, isClosed, visitor, next); 14 | } 15 | /** 16 | * Executes this continuation and calls the next one if exist. 17 | * 18 | * @param model 19 | */ 20 | @Override 21 | public final void execute(Object model) { 22 | if (currentDepth >= 0) { 23 | this.visitor.setIsClosed(isClosed); 24 | this.visitor.setDepth(currentDepth); 25 | } 26 | emitHtml(model); 27 | if (next != null) { 28 | next.execute(model); 29 | } 30 | } 31 | /** 32 | * Hook method to emit HTML. 33 | * 34 | * @param model 35 | */ 36 | protected abstract void emitHtml(Object model); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/continuations/HtmlContinuationSyncCloseAndIndent.java: -------------------------------------------------------------------------------- 1 | package htmlflow.continuations; 2 | 3 | import htmlflow.visitor.HtmlVisitor; 4 | 5 | /** 6 | * @author Pedro Fialho 7 | **/ 8 | public class HtmlContinuationSyncCloseAndIndent extends HtmlContinuationSync { 9 | /** 10 | * Sets indentation to -1 to inform that visitor should continue with previous indentation. 11 | * The isClosed is useless here. 12 | */ 13 | public HtmlContinuationSyncCloseAndIndent(HtmlVisitor visitor, HtmlContinuation next) { 14 | super(-1, false, visitor, next); // The isClosed parameter is useless in this case 15 | } 16 | public HtmlContinuationSyncCloseAndIndent(HtmlVisitor visitor) { 17 | super(-1, false, visitor, null); // The isClosed parameter is useless in this case 18 | } 19 | 20 | @Override 21 | protected final void emitHtml(Object model) { 22 | /** 23 | * !!!!! This continuation may follow a dynamic or await block that may create a block or inline element. 24 | * Block elements increment (depth) indentation at the beginning and decrement at the end. 25 | * On the other hand, inline elements keep indentation (depth) unmodified. 26 | * We assume most dynamic or await blocks will create block elements. 27 | * Thus, here we start by decrementing depth. 28 | * However, this could not be true for some cases, and we will get inconsistent indentation !!!! 29 | */ 30 | visitor.setDepth(visitor.getDepth() - 1); 31 | visitor.newlineAndIndent(); 32 | } 33 | 34 | @Override 35 | public HtmlContinuation copy(HtmlVisitor visitor) { 36 | return new HtmlContinuationSyncCloseAndIndent( 37 | visitor, 38 | next != null ? next.copy(visitor) : null); // call copy recursively 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/continuations/HtmlContinuationSyncDynamic.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-2022, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte) 5 | * and Pedro Fialho. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package htmlflow.continuations; 27 | 28 | import htmlflow.visitor.HtmlVisitor; 29 | import org.xmlet.htmlapifaster.Element; 30 | 31 | import java.util.function.BiConsumer; 32 | 33 | /** 34 | * HtmlContinuation for a dynamic block (i.e. BiConsumer) depending of an object model. 35 | * 36 | * @param the type of the parent HTML element received by the dynamic HTML block. 37 | * @param the type of the template's model. 38 | */ 39 | public class HtmlContinuationSyncDynamic extends HtmlContinuationSync { 40 | 41 | /** 42 | * The continuation that consumes the element and a model. 43 | */ 44 | final BiConsumer consumer; 45 | /** 46 | * The element passed to the continuation consumer. 47 | */ 48 | final E element; 49 | 50 | /** 51 | * @param currentDepth Indentation depth associated to this block. 52 | * @param consumer The continuation that consumes the element and a model. 53 | */ 54 | public HtmlContinuationSyncDynamic( 55 | int currentDepth, 56 | boolean isClosed, 57 | E element, BiConsumer consumer, 58 | HtmlVisitor visitor, 59 | HtmlContinuation next 60 | ) { 61 | super(currentDepth, isClosed, visitor, next); 62 | this.element = element; 63 | this.consumer = consumer; 64 | } 65 | @Override 66 | protected final void emitHtml(Object model) { 67 | consumer.accept(element, (T) model); 68 | } 69 | 70 | @Override 71 | public HtmlContinuation copy(HtmlVisitor v) { 72 | return new HtmlContinuationSyncDynamic<>( 73 | currentDepth, 74 | isClosed, 75 | copyElement(element, v), 76 | consumer, 77 | v, 78 | next != null ? next.copy(v) : null); // call copy recursively 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/continuations/HtmlContinuationSyncStatic.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-2022, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte) 5 | * and Pedro Fialho. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package htmlflow.continuations; 27 | 28 | import htmlflow.visitor.HtmlVisitor; 29 | 30 | /** 31 | * HtmlContinuation for a static HTML block. 32 | */ 33 | public class HtmlContinuationSyncStatic extends HtmlContinuationSync { 34 | final String staticHtmlBlock; 35 | /** 36 | * Sets indentation to -1 to inform that visitor should continue with previous indentation. 37 | * The isClosed is useless because it just writes what it is in its staticHtmlBlock. 38 | */ 39 | public HtmlContinuationSyncStatic(String staticHtmlBlock, HtmlVisitor visitor, HtmlContinuation next) { 40 | super(-1, false, visitor, next); // The isClosed parameter is useless in this case of Static HTML block. 41 | this.staticHtmlBlock = staticHtmlBlock.intern(); // Maybe intern() here is useless and implicit by VM 42 | } 43 | 44 | @Override 45 | protected final void emitHtml(Object model) { 46 | visitor.write(staticHtmlBlock); 47 | } 48 | 49 | @Override 50 | public HtmlContinuation copy(HtmlVisitor v) { 51 | return new HtmlContinuationSyncStatic( 52 | staticHtmlBlock, 53 | v, 54 | next != null ? next.copy(v) : null); // call copy recursively 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/exceptions/HtmlFlowAppendException.java: -------------------------------------------------------------------------------- 1 | package htmlflow.exceptions; 2 | 3 | import java.util.function.UnaryOperator; 4 | 5 | /** 6 | * @author Pedro Fialho 7 | * 8 | * Class to be used where is an error while writing to the output 9 | **/ 10 | public class HtmlFlowAppendException extends RuntimeException { 11 | 12 | private static final UnaryOperator EXCEPTION_MESSAGE = msg -> "There has been an exception in writing the Html" + 13 | ", underlying exception is "+msg; 14 | 15 | public HtmlFlowAppendException(Throwable th) { 16 | super(EXCEPTION_MESSAGE.apply(th.getMessage()), th); 17 | } 18 | 19 | public HtmlFlowAppendException(String message) { 20 | super(EXCEPTION_MESSAGE.apply(message)); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/flowifier/DefaultHtmlToJavaHtmlFlowNodeVisitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-19 HtmlFlow.org 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package htmlflow.flowifier; 25 | 26 | /** 27 | * Default implementation of the visitor of a JSoup node that converts the HTML 28 | * source code into a Java class, it uses a StringBuilder to append the content 29 | * of the Java class. Note that this implementation is not intended to be 30 | * thread-safe 31 | * 32 | * @author Julien Gouesse 33 | * 34 | */ 35 | public class DefaultHtmlToJavaHtmlFlowNodeVisitor extends AbstractHtmlToJavaHtmlFlowNodeVisitor { 36 | 37 | /** 38 | * Constructor with indentation disabled 39 | */ 40 | public DefaultHtmlToJavaHtmlFlowNodeVisitor() { 41 | this(false); 42 | } 43 | 44 | /** 45 | * Constructor 46 | * 47 | * @param indented 48 | * true if the generated HTML source code is 49 | * indented, otherwise false 50 | */ 51 | public DefaultHtmlToJavaHtmlFlowNodeVisitor(final boolean indented) { 52 | super(StringBuilder::new, indented); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/flowifier/HtmlToJavaHtmlFlowNodeVisitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-19 HtmlFlow.org 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package htmlflow.flowifier; 25 | 26 | import java.io.IOException; 27 | import java.lang.reflect.Method; 28 | 29 | import org.jsoup.nodes.Attribute; 30 | import org.jsoup.nodes.Node; 31 | import org.jsoup.select.NodeVisitor; 32 | 33 | /** 34 | * Visitor of a JSoup node that converts the HTML source code into a Java class 35 | * 36 | * @author Julien Gouesse 37 | * 38 | * @param 39 | * the type of appendable used to store the Java source code 40 | */ 41 | public interface HtmlToJavaHtmlFlowNodeVisitor extends NodeVisitor { 42 | 43 | /** 44 | * Appends the header of the Java class, i.e the imports, the declaration of 45 | * the class, the declaration of the method, ... 46 | * 47 | * @throws IOException 48 | * thrown when something wrong occurs while appending the Java 49 | * source code 50 | */ 51 | void appendHeader() throws IOException; 52 | 53 | /** 54 | * Appends the footer of the Java class, i.e closes the method and the class 55 | * 56 | * @throws IOException 57 | * thrown when something wrong occurs while appending the Java 58 | * source code 59 | */ 60 | void appendFooter() throws IOException; 61 | 62 | /** 63 | * Appends the attribute value and key 64 | * 65 | * @param attribute 66 | * the attribute 67 | * @param nodeClass 68 | * the class of the node 69 | * @throws IOException 70 | * thrown when something wrong occurs while appending the Java 71 | * source code 72 | */ 73 | void appendAttribute(Attribute attribute, Class nodeClass) throws IOException; 74 | 75 | /** 76 | * Tells whether a JSoup node cannot be closed 77 | * 78 | * @param node 79 | * the JSoup node 80 | * @return true if the JSoup node cannot be closed, otherwise 81 | * false 82 | */ 83 | boolean isUncloseable(Node node); 84 | 85 | /** 86 | * Converts the content of a Java string into a string that can be declared 87 | * in a Java class passed as a method parameter 88 | * 89 | * @param javaStringContent 90 | * the content of a Java string 91 | * @return a string that can be declared in a Java class passed as a method 92 | * parameter 93 | */ 94 | String convertJavaStringContentToJavaDeclarableString(String javaStringContent); 95 | 96 | /** 97 | * Returns the appendable used to store the Java class 98 | * 99 | * @return the appendable used to store the Java class 100 | */ 101 | T getAppendable(); 102 | 103 | /** 104 | * Returns the class of the node whose name is passed if any, otherwise 105 | * null 106 | * 107 | * @param nodeName 108 | * the node name 109 | * @return the class of the node whose name is passed if any, otherwise 110 | * null 111 | */ 112 | Class getClassFromNodeName(String nodeName); 113 | 114 | /** 115 | * Returns the method of the class node that matches with the attribute if 116 | * any, otherwise null 117 | * 118 | * @param nodeClass 119 | * the class of the node 120 | * @param attribute 121 | * the attribute 122 | * @return the method of the class node that matches with the attribute if 123 | * any, otherwise null 124 | */ 125 | Method getMethodFromAttribute(Class nodeClass, Attribute attribute); 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/visitor/HtmlDocVisitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow.visitor; 26 | 27 | import org.xmlet.htmlapifaster.Element; 28 | import org.xmlet.htmlapifaster.SuspendConsumer; 29 | import org.xmlet.htmlapifaster.async.AwaitConsumer; 30 | 31 | import java.util.function.BiConsumer; 32 | 33 | /** 34 | * This is the implementation of the ElementVisitor (from HtmlApiFaster 35 | * library) that emits HTML immediately with no optimizations. 36 | * 37 | * @author Miguel Gamboa 38 | * created on 04-08-2022 39 | */ 40 | public class HtmlDocVisitor extends HtmlVisitor { 41 | 42 | public HtmlDocVisitor(Appendable out, boolean isIndented) { 43 | this(out, isIndented, 0); 44 | } 45 | 46 | public HtmlDocVisitor(Appendable out,boolean isIndented, int depth) { 47 | super(out, isIndented); 48 | this.out = out; 49 | this.depth = depth; 50 | } 51 | 52 | @Override 53 | public final void visitDynamic(E element, BiConsumer dynamicHtmlBlock) { 54 | throw new IllegalStateException("Wrong use of dynamic() in a static view! Use HtmlView to produce a dynamic view."); 55 | } 56 | 57 | @Override 58 | public final void visitAwait(E element, AwaitConsumer asyncAction) { 59 | throw new IllegalStateException("Wrong use of async() in a static view! Use HtmlViewAsync to produce an async view."); 60 | } 61 | 62 | @Override 63 | public void visitSuspending(E element, SuspendConsumer suspendAction) { 64 | throw new IllegalStateException("Wrong use of suspending() in a static view! Use HtmlViewSuspend to produce an async view."); 65 | } 66 | 67 | @Override 68 | public void resolve(Object model) { 69 | throw new UnsupportedOperationException("HTML has been already emitted on elements flow. " + 70 | "resolve() is only available for HtmlView pages."); 71 | } 72 | 73 | @Override 74 | public final HtmlDocVisitor clone(boolean isIndented) { 75 | return new HtmlDocVisitor(this.out, isIndented); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/visitor/HtmlViewVisitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-22, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow.visitor; 26 | 27 | import htmlflow.continuations.HtmlContinuation; 28 | import org.xmlet.htmlapifaster.Element; 29 | import org.xmlet.htmlapifaster.SuspendConsumer; 30 | import org.xmlet.htmlapifaster.async.AwaitConsumer; 31 | 32 | import java.util.function.BiConsumer; 33 | 34 | 35 | /** 36 | * This is the base implementation of the ElementVisitor (from HtmlApiFaster library). 37 | * 38 | * HtmlViewVisitor is also a head of HtmlContinuation objects chain, storing all 39 | * needed information to emit HTML corresponding to Static HTML blocks and dynamic HTML. 40 | * 41 | * @author Miguel Gamboa, Luís Duare, Pedro Fialho 42 | * created on 17-01-2018 43 | */ 44 | public class HtmlViewVisitor extends HtmlVisitor { 45 | /** 46 | * The first node to be processed. 47 | */ 48 | public final HtmlContinuation first; 49 | 50 | public HtmlViewVisitor(Appendable out, boolean isIndented, HtmlContinuation first) { 51 | super(out, isIndented); 52 | this.first = first.copy(this); 53 | } 54 | 55 | /** 56 | * Processing output through invocation of HtmlContinuation objects chain. 57 | */ 58 | @Override 59 | public final void resolve(Object model) { 60 | first.execute(model); 61 | } 62 | 63 | @Override 64 | public HtmlVisitor clone(boolean isIndented) { 65 | return new HtmlViewVisitor(out, isIndented, first); 66 | } 67 | 68 | /** 69 | * We only allow a single call to visitDynamic when we are preprocessing the template function 70 | * and building an invocation chain of HtmlContinuation objects (see PreprocessingVisitor). 71 | * 72 | * @param element The parent Element. 73 | * @param dynamicHtmlBlock The continuation that consumes the element and a model. 74 | */ 75 | @Override 76 | public final void visitDynamic(E element, BiConsumer dynamicHtmlBlock) { 77 | /** 78 | * After first resolution we will only invoke the dynamicHtmlBlock consumer and no more visits 79 | * to dynamic can happen. 80 | * Otherwise, maybe we are invoking a dynamic nested in other dynamic, which is not allowed! 81 | */ 82 | throw new IllegalStateException("You are already in a dynamic block! Do not use dynamic() chained inside another dynamic!"); 83 | } 84 | 85 | @Override 86 | public void visitAwait(E element, AwaitConsumer asyncAction) { 87 | throw new IllegalStateException("Wrong use of await() in a HtmlView! Use HtmlFlow.viewAsync() to produce an async view."); 88 | } 89 | 90 | @Override 91 | public void visitSuspending(E element, SuspendConsumer suspendAction) { 92 | throw new IllegalStateException("Wrong use of suspending() in a HtmlView! Use HtmlFlow.viewSuspend() to produce a suspendable view."); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/visitor/HtmlViewVisitorAsync.java: -------------------------------------------------------------------------------- 1 | package htmlflow.visitor; 2 | 3 | import htmlflow.continuations.HtmlContinuation; 4 | import htmlflow.continuations.HtmlContinuationAsyncTerminationNode; 5 | 6 | import java.util.concurrent.CompletableFuture; 7 | 8 | import static htmlflow.visitor.PreprocessingVisitor.HtmlContinuationSetter.setNext; 9 | 10 | 11 | /** 12 | * @author Pedro Fialho 13 | **/ 14 | public class HtmlViewVisitorAsync extends HtmlViewVisitor { 15 | 16 | /** 17 | * The last node to be processed. 18 | */ 19 | protected final HtmlContinuation last; 20 | 21 | public HtmlViewVisitorAsync(boolean isIndented, HtmlContinuation first) { 22 | /** 23 | * Intentionally pass null to out Appendable. 24 | * Since this visitor allows concurrency, due to its asynchronous nature, then we must 25 | * clone it on each resolution (i.e. finishAsync()) to avoid sharing continuations across 26 | * different tasks and set a new out Appendable. 27 | */ 28 | this(null, isIndented, first); 29 | } 30 | 31 | /** 32 | * Auxiliary for clone. 33 | */ 34 | private HtmlViewVisitorAsync(Appendable out, boolean isIndented, HtmlContinuation copy) { 35 | super(out, isIndented, copy); 36 | this.last = findLast(); 37 | } 38 | 39 | @Override 40 | public HtmlViewVisitorAsync clone(boolean isIndented) { 41 | return new HtmlViewVisitorAsync(out, isIndented, first); 42 | } 43 | 44 | public HtmlViewVisitorAsync clone(Appendable out) { 45 | return new HtmlViewVisitorAsync(out, isIndented, first); 46 | } 47 | 48 | public CompletableFuture finishedAsync(Object model) { 49 | CompletableFuture cf = new CompletableFuture<>(); 50 | HtmlContinuationAsyncTerminationNode terminationNode = new HtmlContinuationAsyncTerminationNode(cf); 51 | /** 52 | * Chain terminationNode next to the last node. 53 | * Keep last pointing to the same node to replace the terminationNode on 54 | * others render async. 55 | */ 56 | setNext(last, terminationNode); 57 | /** 58 | * Initializes render on first node. 59 | */ 60 | this.first.execute(model); 61 | 62 | return cf; 63 | } 64 | 65 | private HtmlContinuation findLast() { 66 | HtmlContinuation node = this.first; 67 | 68 | while (node.next != null){ 69 | node = node.next; 70 | } 71 | 72 | return node; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/visitor/Indentation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow.visitor; 26 | 27 | 28 | import static htmlflow.visitor.Tags.FINISH_TAG; 29 | 30 | /** 31 | * @author Luís Duarte created on 9-09-2018 32 | */ 33 | class Indentation { 34 | 35 | private static final int MAX_TABS = 1000; 36 | private static final char NEWLINE = '\n'; 37 | private static final char TAB = '\t'; 38 | private static final String[] tabs = createTabs(MAX_TABS); 39 | private static final String[] closedTabs = createClosedTabs(MAX_TABS); 40 | 41 | private Indentation(){ } 42 | 43 | private static String[] createTabs(int tabsMax){ 44 | String[] tabs = new String[tabsMax]; 45 | 46 | for (int i = 0; i < tabsMax; i++) { 47 | char[] newTab = new char[i + 1]; 48 | newTab[0] = NEWLINE; 49 | 50 | tabs[i] = new String(newTab).replace('\0', TAB); 51 | } 52 | 53 | return tabs; 54 | } 55 | 56 | private static String[] createClosedTabs(int tabsMax){ 57 | String[] closedTabs = new String[tabsMax]; 58 | 59 | for (int i = 0; i < tabsMax; i++) { 60 | 61 | char[] newClosedTab = new char[i + 2]; 62 | newClosedTab[0] = FINISH_TAG; 63 | newClosedTab[1] = NEWLINE; 64 | 65 | closedTabs[i] = new String(newClosedTab).replace('\0', TAB); 66 | } 67 | 68 | return closedTabs; 69 | } 70 | 71 | public static String tabs(int depth){ 72 | return tabs[depth]; 73 | } 74 | 75 | public static String closedTabs(int depth) { 76 | return closedTabs[depth]; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/visitor/PreprocessingVisitorAsync.java: -------------------------------------------------------------------------------- 1 | package htmlflow.visitor; 2 | 3 | import htmlflow.continuations.HtmlContinuation; 4 | import htmlflow.continuations.HtmlContinuationAsync; 5 | import htmlflow.continuations.HtmlContinuationSyncCloseAndIndent; 6 | import org.xmlet.htmlapifaster.Element; 7 | import org.xmlet.htmlapifaster.SuspendConsumer; 8 | import org.xmlet.htmlapifaster.async.AwaitConsumer; 9 | 10 | 11 | /** 12 | * @author Pedro Fialho 13 | **/ 14 | public class PreprocessingVisitorAsync extends PreprocessingVisitor { 15 | 16 | public PreprocessingVisitorAsync(boolean isIndented) { 17 | super(isIndented); 18 | } 19 | 20 | @Override 21 | public void visitAwait(E element, AwaitConsumer asyncHtmlBlock) { 22 | /** 23 | * Creates an HtmlContinuation for the async block. 24 | */ 25 | HtmlContinuation asyncCont = new HtmlContinuationAsync<>( 26 | depth, 27 | isClosed, 28 | element, 29 | asyncHtmlBlock, 30 | this, 31 | new HtmlContinuationSyncCloseAndIndent(this)); 32 | /** 33 | * We are resolving this view for the first time. 34 | * Now we just need to create an HtmlContinuation corresponding to the previous static HTML, 35 | * which will be followed by the asyncCont. 36 | */ 37 | chainContinuationStatic(asyncCont); 38 | /** 39 | * We have to run newlineAndIndent to leave isClosed and indentation correct for 40 | * the next static HTML block. 41 | */ 42 | indentAndAdvanceStaticBlockIndex(); 43 | } 44 | 45 | @Override 46 | public void visitSuspending(E element, SuspendConsumer suspendAction) { 47 | throw new UnsupportedOperationException("Illegal use of suspending builder. To use it you should create a HtmlViewSuspend."); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/htmlflow/visitor/Tags.java: -------------------------------------------------------------------------------- 1 | package htmlflow.visitor; 2 | 3 | import htmlflow.exceptions.HtmlFlowAppendException; 4 | 5 | import java.io.IOException; 6 | 7 | public class Tags { 8 | static final char BEGIN_TAG = '<'; 9 | static final String BEGIN_CLOSE_TAG = ""; 12 | static final String ATTRIBUTE_MID = "=\""; 13 | static final char FINISH_TAG = '>'; 14 | static final char SPACE = ' '; 15 | static final char QUOTATION = '"'; 16 | 17 | private Tags() { 18 | } 19 | /** 20 | * Write {@code ""}. 33 | */ 34 | public static void endTag(Appendable out, String elementName) { 35 | try { 36 | out.append(BEGIN_CLOSE_TAG); // 39 | } catch (IOException e) { 40 | throw new HtmlFlowAppendException(e); 41 | } 42 | } 43 | 44 | /** 45 | * Writes {@code "attributeName=attributeValue"} 46 | */ 47 | public static void addAttribute(Appendable out, String attributeName, String attributeValue) { 48 | try { 49 | out.append(SPACE); 50 | out.append(attributeName); 51 | out.append(ATTRIBUTE_MID); 52 | out.append(attributeValue); 53 | out.append(QUOTATION); 54 | }catch (IOException e) { 55 | throw new HtmlFlowAppendException(e); 56 | } 57 | } 58 | 59 | /** 60 | * Writes {@code ""} 61 | */ 62 | public static void addComment(Appendable out, String comment) { 63 | try { 64 | out.append(BEGIN_COMMENT_TAG); // 67 | } catch (IOException e) { 68 | throw new HtmlFlowAppendException(e); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/kotlin/htmlflow/HtmlFlowExtensions.kt: -------------------------------------------------------------------------------- 1 | package htmlflow 2 | 3 | //import jdk.internal.org.jline.utils.ShutdownHooks 4 | import htmlflow.continuations.HtmlContinuationSuspendableTerminationNode 5 | import htmlflow.visitor.HtmlVisitor 6 | import htmlflow.visitor.PreprocessingVisitor 7 | import org.xmlet.htmlapifaster.Element 8 | import org.xmlet.htmlapifaster.Html 9 | import org.xmlet.htmlapifaster.Text 10 | 11 | /** 12 | * Alternative close tag function for `__()`. 13 | */ 14 | inline val , Z : Element<*,*>> T.l: Z 15 | get() = this.`__`() 16 | 17 | /** 18 | * Root property of HTML element. 19 | */ 20 | inline val HtmlPage.html: Html 21 | get() { 22 | (this.visitor as HtmlVisitor).write(HtmlPage.HEADER) 23 | return Html(self()) 24 | } 25 | 26 | /** 27 | * Root builder of HTML element with lambda with receiver. 28 | */ 29 | inline fun HtmlPage.html(block : Html.() -> Unit) : HtmlPage { 30 | (this.visitor as HtmlVisitor).write(HtmlPage.HEADER) 31 | return Html(self()).also { it.block() }.l 32 | } 33 | 34 | /** 35 | * Text node property. 36 | */ 37 | inline var , Z : Element<*,*>> T.text : T 38 | get() = self() 39 | set(value) { 40 | visitor.visitText( 41 | Text( 42 | self(), 43 | visitor, 44 | value, 45 | ) 46 | ) 47 | } 48 | 49 | /** 50 | * @param T type of the Element receiver 51 | * @param Z type of the parent Element of the receiver 52 | * @param M type of the model (aka context object) 53 | */ 54 | inline fun , Z : Element<*,*>, M> Element.dyn(crossinline cons: T.(M) -> Unit): T { 55 | this.dynamic { elem, model -> 56 | elem.cons(model) 57 | } 58 | return this.self() 59 | } 60 | 61 | /** 62 | * @param T type of the Element receiver 63 | * @param Z type of the parent Element of the receiver 64 | * @param M type of the model (aka context object) 65 | */ 66 | inline fun , Z : Element<*,*>, M> Element.await(crossinline cons: T.(M, () -> Unit) -> Unit): T { 67 | this.await { elem, model, cb -> 68 | elem.cons(model) { cb.finish() } 69 | } 70 | return this.self() 71 | } 72 | 73 | /** 74 | * Appendable extension to build an HtmlDoc. 75 | */ 76 | fun Appendable.doc(block: HtmlDoc.() -> Unit): HtmlDoc = HtmlFlow.doc(this).also(block) 77 | 78 | /** 79 | * @param M type of the model (aka context object) 80 | */ 81 | fun view(template: HtmlPage.() -> Unit) = HtmlFlow.view(template) 82 | 83 | /** 84 | * @param M type of the model (aka context object) 85 | */ 86 | fun viewAsync(template: HtmlPage.() -> Unit) = HtmlFlow.viewAsync(template) 87 | 88 | /** 89 | * @param M type of the model (aka context object) 90 | */ 91 | fun viewSuspend(template: HtmlPage.() -> Unit) = viewSuspend( 92 | template, 93 | isIndented = true, 94 | threadSafe = false 95 | ) 96 | 97 | fun viewSuspend(template: HtmlPage.() -> Unit, isIndented: Boolean, threadSafe: Boolean): HtmlViewSuspend { 98 | val pre = preprocessingSuspend(template, isIndented) 99 | val visitor = HtmlViewVisitorSuspend( 100 | isIndented = isIndented, 101 | first = pre.first 102 | ) 103 | /** 104 | * Chain terminationNode next to the last node. 105 | */ 106 | val terminationNode = HtmlContinuationSuspendableTerminationNode() 107 | PreprocessingVisitor.HtmlContinuationSetter.setNext(visitor.findLast(), terminationNode) 108 | return HtmlViewSuspend(template, visitor, threadSafe) 109 | } 110 | 111 | /** 112 | * @param template An HtmlTemplate function, which depends on an HtmlView used to create HTMl elements. 113 | * @param isIndented Set indentation on or off. 114 | */ 115 | private fun preprocessingSuspend(template: HtmlTemplate, isIndented: Boolean): PreprocessingVisitorSuspend { 116 | val pre = PreprocessingVisitorSuspend(isIndented) 117 | val preView: HtmlView<*> = HtmlView({ pre }, template, false) 118 | template.resolve(preView) // ????? Why ????? 119 | /** 120 | * NO problem with null model. We are just preprocessing static HTML blocks. 121 | * Thus, dynamic blocks which depend on model are not invoked. 122 | */ 123 | preView.visitor.resolve(null) 124 | return pre 125 | } 126 | -------------------------------------------------------------------------------- /src/main/kotlin/htmlflow/HtmlFlowSuspending.kt: -------------------------------------------------------------------------------- 1 | package htmlflow 2 | 3 | import org.xmlet.htmlapifaster.Element 4 | import org.xmlet.htmlapifaster.SuspendConsumer 5 | 6 | /** 7 | * @param E This HTML element 8 | */ 9 | suspend fun > E.suspending(block: suspend E.() -> Unit): E { 10 | if(lookForRootElement() !is HtmlDoc) { 11 | throw IllegalStateException("Illegal use of suspending. Using .suspending { elem -> ...} without a model, is only valid in HtmlDoc.") 12 | } 13 | block(this) 14 | return this 15 | } 16 | 17 | /** 18 | * @param E This HTML element 19 | * @param M The type of model. 20 | */ 21 | fun , M> E.suspending(block: suspend E.(M) -> Unit) : E { 22 | /* 23 | * This verification is made in preprocessing using a dummy HtmlView for both sync and async cases. 24 | */ 25 | if(lookForRootElement() is HtmlDoc) { 26 | throw IllegalStateException("Illegal use of suspending in HtmlDoc. Using .suspending { elem, model -> ...} is only valid in HtmlViewAsync.") 27 | } 28 | 29 | this.visitor.visitSuspending(this, object : SuspendConsumer { 30 | override suspend fun E.accept(model: M) { 31 | block(this, model as M) 32 | } 33 | }) 34 | return this 35 | } 36 | 37 | fun > E.lookForRootElement() : HtmlPage { 38 | if(this is HtmlPage) { 39 | return this 40 | } 41 | return this.parent.lookForRootElement() 42 | } -------------------------------------------------------------------------------- /src/main/kotlin/htmlflow/HtmlViewSuspend.kt: -------------------------------------------------------------------------------- 1 | package htmlflow 2 | 3 | import org.xmlet.htmlapifaster.Html 4 | 5 | /** 6 | * Suspendable views can be bound to a domain object with suspending functions based API. 7 | * 8 | * @param Type of the model rendered with this view. 9 | * 10 | * @author Miguel Gamboa 11 | */ 12 | class HtmlViewSuspend( 13 | /** 14 | * Function that consumes an HtmlPage to produce HTML elements. 15 | */ 16 | private val template: HtmlPage.() -> Unit, 17 | private val visitor: HtmlViewVisitorSuspend, 18 | private var threadSafe: Boolean = true 19 | ) : HtmlPage() { 20 | override fun html(): Html { 21 | visitor.write(HEADER) 22 | return Html(this) 23 | } 24 | 25 | override fun setIndented(isIndented: Boolean): HtmlViewSuspend? { 26 | return viewSuspend(template, isIndented, threadSafe) 27 | } 28 | 29 | override fun getVisitor(): HtmlViewVisitorSuspend { 30 | return visitor 31 | } 32 | 33 | override fun getName(): String { 34 | return "HtmlViewSuspend" 35 | } 36 | 37 | override fun threadSafe(): HtmlViewSuspend { 38 | return HtmlViewSuspend(template, visitor) 39 | } 40 | 41 | fun threadUnsafe(): HtmlViewSuspend { 42 | return HtmlViewSuspend(template, visitor, false) 43 | } 44 | 45 | 46 | suspend fun write(out: Appendable, model: M?) { 47 | if (threadSafe) { 48 | visitor.clone(out).first.executeSuspending(model) 49 | } else { 50 | visitor.setAppendable(out) 51 | visitor.first.executeSuspending(model) 52 | } 53 | } 54 | 55 | suspend fun render(): String = render(null) 56 | 57 | suspend fun render(model: M?) = StringBuilder().let { 58 | write(it, model) 59 | it.toString() 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/main/kotlin/htmlflow/HtmlViewVisitorSuspend.kt: -------------------------------------------------------------------------------- 1 | package htmlflow; 2 | 3 | import htmlflow.continuations.HtmlContinuation 4 | import htmlflow.continuations.HtmlContinuationSuspendableTerminationNode 5 | import htmlflow.visitor.HtmlViewVisitor 6 | import htmlflow.visitor.PreprocessingVisitor.HtmlContinuationSetter 7 | 8 | /** 9 | * Intentionally pass null to out Appendable. 10 | * Since this visitor allows concurrency, due to its asynchronous nature, then we must 11 | * clone it on each resolution (i.e. finishAsync()) to avoid sharing continuations across 12 | * different tasks and set a new out Appendable. 13 | */ 14 | class HtmlViewVisitorSuspend( 15 | out: Appendable? = null, 16 | isIndented: Boolean, 17 | first: HtmlContinuation? 18 | ) : HtmlViewVisitor ( 19 | out, isIndented, first 20 | ) { 21 | /** 22 | * The last node to be processed. 23 | */ 24 | private var last: HtmlContinuation? = null 25 | 26 | init { 27 | last = findLast() 28 | } 29 | 30 | override fun clone(isIndented: Boolean): HtmlViewVisitorSuspend { 31 | return HtmlViewVisitorSuspend(out, isIndented, first) 32 | } 33 | 34 | fun clone(out: Appendable): HtmlViewVisitorSuspend { 35 | return HtmlViewVisitorSuspend(out, isIndented, first) 36 | } 37 | 38 | fun findLast(): HtmlContinuation? { 39 | var node = first 40 | while (node.next != null) { 41 | node = node.next 42 | } 43 | return node 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/kotlin/htmlflow/PreprocessingVisitorSuspend.kt: -------------------------------------------------------------------------------- 1 | package htmlflow 2 | 3 | import htmlflow.continuations.* 4 | import htmlflow.visitor.PreprocessingVisitorAsync 5 | import org.xmlet.htmlapifaster.Element 6 | import org.xmlet.htmlapifaster.SuspendConsumer 7 | import org.xmlet.htmlapifaster.async.AwaitConsumer 8 | import java.util.function.BiConsumer 9 | 10 | class PreprocessingVisitorSuspend(isIndented: Boolean) : PreprocessingVisitorAsync(isIndented) { 11 | 12 | /** 13 | * Here we are creating 2 HtmlContinuation objects: one for previous static HTML and a next one 14 | * corresponding to the consumer passed to dynamic(). 15 | * We will first create the dynamic continuation that will be the next node of the static continuation. 16 | * 17 | * U is the type of the model passed to the dynamic HTML block that is the same as T in this visitor. 18 | * Yet, since it came from HtmlApiFaster that is not typed by the Model, then we have to use 19 | * another generic argument for the type of the model. 20 | * 21 | * @param element The parent element. 22 | * @param dynamicHtmlBlock The continuation that consumes the element and a model. 23 | * @param Type of the parent Element. 24 | * @param Type of the model passed to the dynamic HTML block that is the same as T in this visitor. 25 | */ 26 | override fun , U> visitDynamic(element: E, dynamicHtmlBlock: BiConsumer) { 27 | /** 28 | * Creates an HtmlContinuation for the dynamic block. 29 | */ 30 | val dynamicCont: HtmlContinuation = HtmlContinuationSuspendableSyncDynamic( 31 | depth, 32 | isClosed, 33 | element, 34 | dynamicHtmlBlock, 35 | this, 36 | HtmlContinuationSuspendableSyncCloseAndIndent(this) 37 | ) 38 | /** 39 | * We are resolving this view for the first time. 40 | * Now we just need to create an HtmlContinuation corresponding to the previous static HTML, 41 | * which will be followed by the dynamicCont. 42 | */ 43 | chainContinuationSuspendableStatic(dynamicCont) 44 | /** 45 | * We have to run newlineAndIndent to leave isClosed and indentation correct for 46 | * the next static HTML block. 47 | */ 48 | indentAndAdvanceStaticBlockIndex() 49 | } 50 | 51 | 52 | override fun > visitAwait(element: E, asyncHtmlBlock: AwaitConsumer) { 53 | /** 54 | * Creates an HtmlContinuation for a suspending block. 55 | */ 56 | val asyncCont: HtmlContinuation = HtmlContinuationSuspendableAsync( 57 | depth, 58 | isClosed, 59 | element, 60 | asyncHtmlBlock, 61 | this, 62 | HtmlContinuationSuspendableSyncCloseAndIndent(this) 63 | ) 64 | /** 65 | * We are resolving this view for the first time. 66 | * Now we just need to create an HtmlContinuation corresponding to the previous static HTML, 67 | * which will be followed by the suspCont. 68 | */ 69 | chainContinuationSuspendableStatic(asyncCont) 70 | /** 71 | * We have to run newlineAndIndent to leave isClosed and indentation correct for 72 | * the next static HTML block. 73 | */ 74 | indentAndAdvanceStaticBlockIndex() 75 | } 76 | 77 | 78 | override fun > visitSuspending(element: E, suspendAction: SuspendConsumer) { 79 | /** 80 | * Creates an HtmlContinuation for a suspending block. 81 | */ 82 | val suspCont: HtmlContinuation = HtmlContinuationSuspending( 83 | depth, 84 | isClosed, 85 | element, 86 | suspendAction, 87 | this, 88 | HtmlContinuationSuspendableSyncCloseAndIndent(this) 89 | ) 90 | /** 91 | * We are resolving this view for the first time. 92 | * Now we just need to create an HtmlContinuation corresponding to the previous static HTML, 93 | * which will be followed by the suspCont. 94 | */ 95 | chainContinuationSuspendableStatic(suspCont) 96 | /** 97 | * We have to run newlineAndIndent to leave isClosed and indentation correct for 98 | * the next static HTML block. 99 | */ 100 | indentAndAdvanceStaticBlockIndex() 101 | } 102 | 103 | /** 104 | * Creates the last static HTML block. 105 | */ 106 | override fun resolve(model: Any?) { 107 | val staticHtml = sb().substring(staticBlockIndex) 108 | val staticCont: HtmlContinuation = HtmlContinuationSuspendableSyncStatic(staticHtml.trim { it <= ' ' }, this, null) 109 | last = if (first == null) staticCont.also { 110 | first = it // assign both first and last 111 | } else HtmlContinuationSetter.setNext( 112 | last, 113 | staticCont 114 | ) // append new staticCont and return it to be the new last continuation. 115 | } 116 | 117 | private fun chainContinuationSuspendableStatic(nextContinuation: HtmlContinuation) { 118 | val staticHtml = sb().substring(staticBlockIndex) 119 | val staticHtmlTrimmed = staticHtml.trim { it <= ' ' } // trim to remove the indentation from static block 120 | val staticCont: HtmlContinuation = HtmlContinuationSuspendableSyncStatic(staticHtmlTrimmed, this, nextContinuation) 121 | if (first == null) first = staticCont // on first visit initializes the first pointer 122 | else HtmlContinuationSetter.setNext(last, staticCont) // else append the staticCont to existing chain 123 | last = nextContinuation.next // advance last to point to the new HtmlContinuationCloseAndIndent 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendable.kt: -------------------------------------------------------------------------------- 1 | package htmlflow.continuations 2 | 3 | @Suppress("FINITE_BOUNDS_VIOLATION_IN_JAVA") 4 | open class HtmlContinuationSuspendable { 5 | open suspend fun executeSuspending(model: Any?) { 6 | throw UnsupportedOperationException("Illegal use of executeSuspending!") 7 | } 8 | } -------------------------------------------------------------------------------- /src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendableAsync.kt: -------------------------------------------------------------------------------- 1 | package htmlflow.continuations 2 | 3 | import htmlflow.visitor.HtmlVisitor 4 | import kotlinx.coroutines.future.await 5 | import org.xmlet.htmlapifaster.Element 6 | import org.xmlet.htmlapifaster.async.AwaitConsumer 7 | import java.lang.UnsupportedOperationException 8 | import java.util.concurrent.CompletableFuture 9 | 10 | 11 | @Suppress("FINITE_BOUNDS_VIOLATION_IN_JAVA") 12 | class HtmlContinuationSuspendableAsync, T>( 13 | currentDepth: Int, 14 | isClosed: Boolean, 15 | private val element: E, 16 | private val consumer: AwaitConsumer, 17 | visitor: HtmlVisitor, 18 | next: HtmlContinuation? 19 | ) : HtmlContinuation( 20 | currentDepth, isClosed, visitor, next 21 | ) { 22 | override suspend fun executeSuspending(model: Any?) { 23 | if (currentDepth >= 0) { 24 | visitor.setIsClosed(isClosed) 25 | visitor.depth = currentDepth 26 | } 27 | val cf = CompletableFuture() 28 | consumer.accept(element, model as T) { 29 | cf.complete(null) 30 | } 31 | cf.await() 32 | next?.executeSuspending(model) 33 | } 34 | 35 | override fun execute(model: Any?) { 36 | throw UnsupportedOperationException("Illegal use of execute in suspendable continuation! Should use executeSuspending.") 37 | } 38 | 39 | override fun copy(v: HtmlVisitor): HtmlContinuation? { 40 | return HtmlContinuationSuspendableAsync ( 41 | currentDepth, 42 | isClosed, 43 | copyElement(element, v), 44 | consumer, 45 | v, 46 | next?.copy(v) 47 | ) // call copy recursively 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendableSync.kt: -------------------------------------------------------------------------------- 1 | package htmlflow.continuations 2 | 3 | import htmlflow.visitor.HtmlVisitor 4 | 5 | /** 6 | * @author Miguel Gamboa 7 | */ 8 | abstract class HtmlContinuationSuspendableSync( 9 | currentDepth: Int, 10 | isClosed: Boolean, 11 | visitor: HtmlVisitor, 12 | next: HtmlContinuation? 13 | ) : HtmlContinuation(currentDepth, isClosed, visitor, next 14 | ) { 15 | /** 16 | * Executes this continuation and calls the next one if exist. 17 | * 18 | * @param model 19 | */ 20 | override fun execute(model: Any?) { 21 | throw UnsupportedOperationException("Illegal use of execute in suspendable continuation! Should use executeSuspending.") 22 | } 23 | 24 | final override suspend fun executeSuspending(model: Any?) { 25 | if (currentDepth >= 0) { 26 | visitor.setIsClosed(isClosed) 27 | visitor.depth = currentDepth 28 | } 29 | emitHtml(model) 30 | next?.executeSuspending(model) 31 | } 32 | 33 | /** 34 | * Hook method to emit HTML. 35 | * 36 | * @param model 37 | */ 38 | protected abstract fun emitHtml(model: Any?) 39 | } -------------------------------------------------------------------------------- /src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendableSyncCloseAndIndent.kt: -------------------------------------------------------------------------------- 1 | package htmlflow.continuations 2 | 3 | import htmlflow.visitor.HtmlVisitor 4 | 5 | /** 6 | * Sets indentation to -1 to inform that visitor should continue with previous indentation. 7 | * The isClosed is useless here. 8 | * 9 | * @author Miguel Gamboa 10 | */ 11 | class HtmlContinuationSuspendableSyncCloseAndIndent( 12 | visitor: HtmlVisitor, 13 | next: HtmlContinuation? = null 14 | ) : HtmlContinuationSuspendableSync(-1, false, visitor, next) { 15 | 16 | override fun emitHtml(model: Any?) { 17 | /** 18 | * !!!!! This continuation may follow a dynamic or await block that may create a block or inline element. 19 | * Block elements increment (depth) indentation at the beginning and decrement at the end. 20 | * On the other hand, inline elements keep indentation (depth) unmodified. 21 | * We assume most dynamic or await blocks will create block elements. 22 | * Thus, here we start by decrementing depth. 23 | * However, this could not be true for some cases, and we will get inconsistent indentation !!!! 24 | */ 25 | visitor.depth = visitor.depth - 1 26 | visitor.newlineAndIndent() 27 | 28 | } 29 | 30 | override fun copy(visitor: HtmlVisitor): HtmlContinuation? { 31 | return HtmlContinuationSuspendableSyncCloseAndIndent( 32 | visitor, 33 | next?.copy(visitor) 34 | ) // call copy recursively 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendableSyncDynamic.kt: -------------------------------------------------------------------------------- 1 | package htmlflow.continuations 2 | 3 | import htmlflow.visitor.HtmlVisitor 4 | import org.xmlet.htmlapifaster.Element 5 | import java.util.function.BiConsumer 6 | 7 | @Suppress("FINITE_BOUNDS_VIOLATION_IN_JAVA") 8 | class HtmlContinuationSuspendableSyncDynamic, T>( 9 | currentDepth: Int, 10 | isClosed: Boolean, 11 | /** 12 | * The element passed to the continuation consumer. 13 | */ 14 | private val element: E, 15 | /** 16 | * The continuation that consumes the element and a model. 17 | */ 18 | private val consumer: BiConsumer, 19 | visitor: HtmlVisitor, 20 | next: HtmlContinuation?, 21 | 22 | ) : HtmlContinuationSuspendableSync(currentDepth, isClosed, visitor, next) { 23 | override fun emitHtml(model: Any?) { 24 | consumer.accept(element, model as T) 25 | } 26 | 27 | override fun copy(v: HtmlVisitor): HtmlContinuation { 28 | return HtmlContinuationSuspendableSyncDynamic( 29 | currentDepth, 30 | isClosed, 31 | copyElement(element, v), 32 | consumer, 33 | v, 34 | next?.copy(v) 35 | ) // call copy recursively 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendableSyncStatic.kt: -------------------------------------------------------------------------------- 1 | package htmlflow.continuations 2 | 3 | import htmlflow.visitor.HtmlVisitor 4 | 5 | /** 6 | * HtmlContinuationSuspendable for a static HTML block. 7 | * Sets indentation to -1 to inform that visitor should continue with previous indentation. 8 | * The isClosed is useless because it just writes what it is in its staticHtmlBlock. 9 | * @author Miguel Gamboa 10 | */ 11 | class HtmlContinuationSuspendableSyncStatic( 12 | private val staticHtmlBlock: String, 13 | visitor: HtmlVisitor, 14 | next: HtmlContinuation? 15 | ) : HtmlContinuationSuspendableSync(-1, false, visitor, next) { // The isClosed parameter is useless in this case of Static HTML block. 16 | 17 | override fun emitHtml(model: Any?) { 18 | visitor.write(staticHtmlBlock) 19 | } 20 | 21 | override fun copy(v: HtmlVisitor): HtmlContinuation? { 22 | return HtmlContinuationSuspendableSyncStatic( 23 | staticHtmlBlock, 24 | v, 25 | next?.copy(v) 26 | ) // call copy recursively 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendableTerminationNode.kt: -------------------------------------------------------------------------------- 1 | package htmlflow.continuations 2 | 3 | import htmlflow.visitor.HtmlVisitor 4 | 5 | class HtmlContinuationSuspendableTerminationNode : HtmlContinuation(-1, false, null, null) { 6 | 7 | override suspend fun executeSuspending(model: Any?) { 8 | // Nothing to do 9 | } 10 | 11 | override fun execute(model: Any?) { 12 | throw UnsupportedOperationException("Illegal use of suspending terminal node! Only valid in HtmlViewSuspend.") 13 | } 14 | 15 | /** 16 | * Since this Node is used only to signal completion, and we do not use the visitor, 17 | * then we van reuse it. 18 | */ 19 | override fun copy(visitor: HtmlVisitor?): HtmlContinuationSuspendableTerminationNode { 20 | return this 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspending.kt: -------------------------------------------------------------------------------- 1 | package htmlflow.continuations 2 | 3 | import htmlflow.visitor.HtmlVisitor 4 | import org.xmlet.htmlapifaster.Element 5 | import org.xmlet.htmlapifaster.SuspendConsumer 6 | 7 | /** 8 | * HtmlContinuation for a suspending block (i.e.SuspendConsumer) depending of an asynchronous object model. 9 | * The next continuation will be invoked on completion of the asynchronous object model. 10 | * 11 | * @param the type of the parent HTML element received by the dynamic HTML block. 12 | * @param the type of the template's model. 13 | */ 14 | @Suppress("FINITE_BOUNDS_VIOLATION_IN_JAVA") 15 | class HtmlContinuationSuspending, T>( 16 | currentDepth: Int, 17 | isClosed: Boolean, 18 | val element: E, 19 | private val consumer: SuspendConsumer, 20 | visitor: HtmlVisitor, 21 | next: HtmlContinuation? 22 | ) : HtmlContinuation( 23 | currentDepth, isClosed, visitor, next 24 | ) { 25 | 26 | override fun execute(model: Any?) { 27 | throw UnsupportedOperationException("Illegal use fo suspending continuation. This should be used with executeSuspend!") 28 | } 29 | 30 | override suspend fun executeSuspending(model: Any?) { 31 | if (currentDepth >= 0) { 32 | this.visitor.setIsClosed(isClosed) 33 | this.visitor.depth = currentDepth 34 | } 35 | /* 36 | * Call this consumer 37 | */ 38 | this.consumer.run { 39 | element.accept(model as T?) 40 | } 41 | /* 42 | * Call next consumer 43 | */ 44 | next.executeSuspending(model) 45 | } 46 | 47 | override fun copy(v: HtmlVisitor): HtmlContinuation { 48 | return HtmlContinuationSuspending( 49 | currentDepth, 50 | isClosed, 51 | copyElement(element, v), 52 | consumer, 53 | v, 54 | next?.copy(v) 55 | ) // call copy recursively 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/resources/templates/HtmlView-Header.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/exceptions/HtmlFlowAppendExceptionTest.java: -------------------------------------------------------------------------------- 1 | package htmlflow.exceptions; 2 | 3 | import org.junit.jupiter.api.DisplayNameGeneration; 4 | import org.junit.jupiter.api.DisplayNameGenerator; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | /** 10 | * @author Pedro Fialho 11 | **/ 12 | @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) 13 | class HtmlFlowAppendExceptionTest { 14 | 15 | @Test 16 | void given_underlying_exception_should_append_to_error_message() { 17 | Exception ex = new RuntimeException("oops"); 18 | 19 | HtmlFlowAppendException htmlFlowAppendException = new HtmlFlowAppendException(ex); 20 | 21 | assertEquals("There has been an exception in writing the Html" + 22 | ", underlying exception is oops", htmlFlowAppendException.getMessage()); 23 | } 24 | 25 | @Test 26 | void given_message_should_append_to_error_message() { 27 | HtmlFlowAppendException htmlFlowAppendException = new HtmlFlowAppendException("oops"); 28 | 29 | assertEquals("There has been an exception in writing the Html" + 30 | ", underlying exception is oops", htmlFlowAppendException.getMessage()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/TestAsyncViewInConcurInMultpleThreads.java: -------------------------------------------------------------------------------- 1 | package htmlflow.test; 2 | 3 | import htmlflow.HtmlFlow; 4 | import htmlflow.HtmlPage; 5 | import htmlflow.HtmlView; 6 | import htmlflow.HtmlViewAsync; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.util.function.Supplier; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | 13 | class TestAsyncViewInConcurInMultpleThreads { 14 | 15 | @Test 16 | void testMultipleThreadsInViewAsync() throws InterruptedException { 17 | HtmlViewAsync view = HtmlFlow.viewAsync(TestAsyncViewInConcurInMultpleThreads::template).threadSafe(); 18 | checkRender(() -> view.renderAsync().join()); 19 | } 20 | 21 | @Test 22 | void testMultipleThreadsInView() throws InterruptedException { 23 | HtmlView view = HtmlFlow.view(TestAsyncViewInConcurInMultpleThreads::template).threadSafe(); 24 | checkRender(view::render); 25 | } 26 | 27 | private void checkRender(Supplier render) throws InterruptedException { 28 | final int threadCount = 50; 29 | Thread[] thread = new Thread[threadCount]; 30 | String[] html = new String[threadCount]; 31 | for (int i = 0; i < threadCount; i++) { 32 | final int threadNumber = i; 33 | thread[i] = new Thread(() -> { 34 | try { 35 | html[threadNumber] = render.get(); 36 | } catch (Exception e) { 37 | e.printStackTrace(); 38 | } 39 | }); 40 | } 41 | for (int i = 0; i < threadCount; i++) { 42 | thread[i].start(); 43 | } 44 | for (int i = 0; i < threadCount; i++) { 45 | thread[i].join(); 46 | assertEquals(EXPECTED_HTML, html[i]); 47 | } 48 | } 49 | 50 | @SuppressWarnings("squid:S2925") 51 | static void template(HtmlPage view) { 52 | view.div().span().of(span -> { 53 | try { 54 | Thread.sleep(1); 55 | } catch (InterruptedException e) { 56 | e.printStackTrace(); 57 | } 58 | span.a().attrHref("link").text("text").__().of(s -> { 59 | try { 60 | Thread.sleep(1); 61 | } catch (InterruptedException e) { 62 | e.printStackTrace(); 63 | } 64 | s.a().attrHref("link2").text("text2").__(); 65 | }); 66 | }).__() 67 | .__(); 68 | } 69 | 70 | static final String EXPECTED_HTML = "
\n" + 71 | "\t\n" + 72 | "\t\t\n" + 73 | "\t\t\ttext\n" + 74 | "\t\t\n" + 75 | "\t\t\n" + 76 | "\t\t\ttext2\n" + 77 | "\t\t\n" + 78 | "\t\n" + 79 | "
"; 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/TestAsyncViewInConcurrency.java: -------------------------------------------------------------------------------- 1 | package htmlflow.test; 2 | 3 | import htmlflow.HtmlFlow; 4 | import htmlflow.HtmlViewAsync; 5 | import htmlflow.test.model.AsyncModel; 6 | import htmlflow.test.model.Student; 7 | import org.junit.jupiter.api.DisplayNameGeneration; 8 | import org.junit.jupiter.api.DisplayNameGenerator; 9 | import org.junit.jupiter.api.Test; 10 | import org.reactivestreams.Publisher; 11 | import reactor.core.publisher.Flux; 12 | 13 | import java.time.Duration; 14 | import java.util.Iterator; 15 | import java.util.stream.IntStream; 16 | 17 | import static htmlflow.test.TestAsyncView.randomNameGenerator; 18 | import static java.lang.Math.toIntExact; 19 | import static java.util.stream.Collectors.toList; 20 | import static org.junit.jupiter.api.Assertions.assertEquals; 21 | import static org.junit.jupiter.api.Assertions.assertFalse; 22 | 23 | @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) 24 | class TestAsyncViewInConcurrency { 25 | static final int NR_OF_TASKS = Runtime.getRuntime().availableProcessors(); 26 | static final Publisher studentFlux = Flux.range(1, 5) 27 | .delayElements(Duration.ofMillis(10)) 28 | .map(nr -> new Student(nr, randomNameGenerator(toIntExact(nr)))); 29 | static final Publisher titlesFlux = Flux.fromArray(new String[]{"Nr", "Name"}); 30 | 31 | @Test 32 | void check_asyncview_processing_in_sequential_tasks_and_unsafe_view() { 33 | /** 34 | * Arrange View 35 | */ 36 | final HtmlViewAsync view = HtmlFlow.viewAsync(TestAsyncView::testAsyncModel).threadUnsafe(); 37 | /** 38 | * Act and Assert 39 | * Since Stream is Lazy then there is a vertical processing and a sequential execution between tasks. 40 | */ 41 | IntStream 42 | .range(0, NR_OF_TASKS) 43 | .mapToObj(i -> view.renderAsync(new AsyncModel<>(titlesFlux, studentFlux))) 44 | .forEach(cf ->assertHtml(cf.join())); 45 | } 46 | @Test 47 | void check_asyncview_processing_in_concurrent_tasks_and_parallel_threads() { 48 | /** 49 | * Arrange View 50 | */ 51 | final HtmlViewAsync view = HtmlFlow.viewAsync(TestAsyncView::testAsyncModel).threadSafe(); 52 | /** 53 | * Act and Assert 54 | * Collects to dispatch resolution through writeAsync() concurrently. 55 | */ 56 | IntStream 57 | .range(0, NR_OF_TASKS) 58 | .parallel() 59 | .mapToObj(i -> view.renderAsync(new AsyncModel<>(titlesFlux, studentFlux))) 60 | .collect(toList()) 61 | .forEach(cf ->assertHtml(cf.join())); 62 | } 63 | 64 | private static void assertHtml(String html) { 65 | Iterator actual = Utils 66 | .NEWLINE 67 | .splitAsStream(html) 68 | .iterator(); 69 | Utils 70 | .loadLines("asyncTest.html") 71 | .forEach(expected -> { 72 | final String next = actual.next(); 73 | System.out.println(next); 74 | assertEquals(expected, next); 75 | }); 76 | assertFalse(actual.hasNext()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/TestAttributesClassId.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2015-16, Mikael KROK 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | /** 25 | * 26 | */ 27 | package htmlflow.test; 28 | 29 | import htmlflow.HtmlFlow; 30 | import htmlflow.HtmlPage; 31 | import htmlflow.test.views.HtmlLists; 32 | import org.junit.Test; 33 | import org.xmlet.htmlapifaster.Body; 34 | import org.xmlet.htmlapifaster.Div; 35 | import org.xmlet.htmlapifaster.Html; 36 | 37 | import java.io.ByteArrayOutputStream; 38 | import java.io.PrintStream; 39 | import java.util.Iterator; 40 | 41 | import static htmlflow.test.Utils.NEWLINE; 42 | import static org.junit.Assert.assertEquals; 43 | import static org.junit.Assert.assertTrue; 44 | 45 | /** 46 | * @author Mikael KROK 47 | * 48 | */ 49 | public class TestAttributesClassId { 50 | 51 | private static final String DIV_NAME = Div.class.getSimpleName().toLowerCase(); 52 | 53 | @Test 54 | public void testGetElementName() { 55 | Div>> div = HtmlFlow.doc(System.out).html().body().div(); 56 | assertEquals(DIV_NAME, div.getName()); 57 | } 58 | 59 | @Test 60 | public void testIdAndClassAttribute() { 61 | StringBuilder actual = new StringBuilder(); 62 | HtmlLists 63 | .taskView(actual); 64 | assertHtml(actual.toString()); 65 | } 66 | 67 | @Test 68 | public void testIdAndClassAttributeInViewWithPrintStream() { 69 | ByteArrayOutputStream mem = new ByteArrayOutputStream(); 70 | HtmlLists 71 | .taskView(new PrintStream(mem)); 72 | assertHtml(mem.toString()); 73 | } 74 | 75 | private void assertHtml(String actual){ 76 | assertTrue(actual.contains("")); 78 | assertTrue(actual.contains(HtmlLists.DIV_CLASS)); 79 | assertTrue(actual.contains(HtmlLists.DIV_ID)); 80 | assertTrue(actual.contains("toto=\"tutu\"")); 81 | assertTrue("should contains "); 30 | assertEquals(expected, html); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/TestFormAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-24, Miguel Gamboa (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package htmlflow.test; 25 | 26 | import htmlflow.HtmlFlow; 27 | import htmlflow.HtmlPage; 28 | import htmlflow.HtmlViewAsync; 29 | import org.junit.Test; 30 | 31 | import static org.junit.Assert.assertEquals; 32 | 33 | /** 34 | * @author Miguel Gamboa 35 | */ 36 | public class TestFormAttributes { 37 | 38 | /** 39 | * Related with Issue https://github.com/xmlet/HtmlFlow/issues/105 40 | * 41 | * NOT solved yet. setIndent() and threadsafe() are configuration methods 42 | * that should not be invoked in the template function, but instead on the 43 | * HtmlView container. 44 | */ 45 | @Test(expected = StackOverflowError.class) 46 | public void testTextArea() { 47 | // setIndentation invokes the preprocessing, which in turn will execute the 48 | // template function (i.e. the lambda page ->... ) and invoke again 49 | // the setIndentation and henceforward. 50 | HtmlViewAsync view = HtmlFlow.viewAsync((HtmlPage page) -> page 51 | .setIndented(false) 52 | .html() 53 | .body() 54 | .form() 55 | .textarea() 56 | .__() 57 | .__() 58 | .__()); 59 | view.writeAsync(System.out, null).join(); 60 | } 61 | 62 | /** 63 | * Related with Issue https://github.com/xmlet/HtmlFlow/issues/105 64 | */ 65 | @Test 66 | public void testOnEventAttributes() { 67 | final String expected = "" + 68 | "" + 69 | "" + 71 | "" + 72 | ""; 73 | final StringBuilder out = new StringBuilder(); 74 | HtmlFlow 75 | .doc(out) 76 | .setIndented(false) 77 | .html().body().button().attrOncontextmenu("(event) => {}").__().__().__(); 78 | assertEquals(expected, out.toString()); 79 | } 80 | 81 | @Test(expected = IllegalStateException.class) 82 | public void testCheckTextInterleavedInFormWithinAttributes() { 83 | final String style = "width: 89%; height: 77px;"; 84 | HtmlFlow 85 | .doc(System.out) 86 | .html() 87 | .body() 88 | .form() 89 | .textarea() 90 | .addAttr("align", "right") 91 | .attrRows(50l) 92 | .attrCols(50l) 93 | .text("my simple texttext") 94 | .attrId("id") 95 | .attrName("name") 96 | .attrStyle(style) 97 | .__() 98 | .__() 99 | .__(); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/TestHtmlViewAsElement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-16, mcarvalho (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow.test; 26 | 27 | import htmlflow.HtmlDoc; 28 | import htmlflow.HtmlFlow; 29 | import org.junit.Assert; 30 | import org.junit.Test; 31 | 32 | public class TestHtmlViewAsElement { 33 | 34 | @Test 35 | public void testSelf() { 36 | HtmlDoc view = HtmlFlow.doc(System.out); 37 | Assert.assertSame(view, view.self()); 38 | Assert.assertEquals("HtmlDoc", view.getName()); 39 | } 40 | 41 | @Test(expected = IllegalStateException.class) 42 | public void testWrong__use() { 43 | HtmlFlow.doc(System.out).__(); 44 | } 45 | 46 | @Test(expected = IllegalStateException.class) 47 | public void testWrongParentUse() { 48 | HtmlFlow.doc(System.out).getParent(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/TestNewScriptTypes.java: -------------------------------------------------------------------------------- 1 | package htmlflow.test; 2 | 3 | import htmlflow.HtmlFlow; 4 | import org.junit.Test; 5 | import org.xmlet.htmlapifaster.EnumTypeScriptType; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | public class TestNewScriptTypes { 10 | static final String EXPECTED_SCRIPT_MODULE = "" + 11 | "" + 12 | "" + 13 | "" + 14 | "" + 15 | "" + 16 | ""; 17 | 18 | @Test 19 | public void view_script_type_module(){ 20 | StringBuilder buffer = new StringBuilder(); 21 | HtmlFlow 22 | .doc(buffer) 23 | .setIndented(false) 24 | .html() 25 | .head() 26 | .script().attrType(EnumTypeScriptType.MODULE).__() 27 | .__() //head 28 | .__(); // html 29 | assertEquals(EXPECTED_SCRIPT_MODULE, buffer.toString()); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/TestReactiveView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-21, Miguel Gamboa (htmlflow.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package htmlflow.test; 25 | 26 | import htmlflow.HtmlFlow; 27 | import htmlflow.HtmlPage; 28 | import htmlflow.HtmlView; 29 | import org.junit.Test; 30 | import org.reactivestreams.Publisher; 31 | import reactor.core.publisher.Flux; 32 | 33 | import java.time.Duration; 34 | import java.time.temporal.ChronoUnit; 35 | import java.util.concurrent.CompletableFuture; 36 | 37 | public class TestReactiveView { 38 | 39 | @java.lang.SuppressWarnings("squid:S2699") 40 | @Test 41 | public void testInconsistentHtmlUsingDynamicWithReactiveModel() { 42 | /** 43 | * Only for unit tests purpose and control completion 44 | */ 45 | CompletableFuture cf = new CompletableFuture<>(); 46 | /** 47 | * Cont from 1 to 5 with 0 delay in 10 milliseconds interval. 48 | */ 49 | Flux nrs = Flux 50 | .interval(Duration.of(10, ChronoUnit.MILLIS)) 51 | .map(n -> n + 1) 52 | .take(5) 53 | .doOnComplete(() -> cf.complete(null)); 54 | HtmlView view = HtmlFlow.view( 55 | System.out, 56 | TestReactiveView::rxViewWithListingFromObservable); 57 | /** 58 | * Act 59 | */ 60 | view.write(nrs); // render view and emit output 61 | cf.join(); // Wait for nrs emit completion 62 | } 63 | 64 | private static void rxViewWithListingFromObservable(HtmlPage view) { 65 | view 66 | .html() 67 | .head().title().text("Reactive Test").__().__() 68 | .body() 69 | .div() 70 | .p().text("Creating a listing from a reactive model: ").__() 71 | .ul() 72 | .>dynamic((ul, model) -> Flux.from(model).subscribe(nr -> ul 73 | .li().text(nr).__() 74 | )) 75 | .__() 76 | .__() 77 | .__() 78 | .__(); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/TestResourceNotFound.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-16, Miguel Gamboa (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow.test; 26 | 27 | import org.junit.Test; 28 | 29 | import java.io.ByteArrayOutputStream; 30 | import java.io.IOException; 31 | import java.io.InputStream; 32 | import java.lang.reflect.Field; 33 | 34 | /** 35 | * Unit Test to coverage the case where ClassLoader does not find 36 | * resource for HTML template. 37 | * To that end we use a custom ClassLoader, the ClassLoaderGw, that 38 | * will fail to load resources. 39 | * We load it and access it via Reflection to avoid Jitter fall in default ClassLoader. 40 | * 41 | * @author Miguel Gamboa 42 | * created on 20-09-2017 43 | */ 44 | public class TestResourceNotFound { 45 | 46 | @Test(expected = ExceptionInInitializerError.class) 47 | public void testHtmlViewHeaderNotFound() 48 | throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException { 49 | ClassLoaderGw gw = new ClassLoaderGw(); 50 | Class klassView = gw.loadClass("htmlflow.HtmlPage"); 51 | Field f = klassView.getDeclaredField("HEADER"); 52 | f.setAccessible(true); 53 | f.get(null); 54 | } 55 | } 56 | 57 | class ClassLoaderGw extends ClassLoader { 58 | 59 | public ClassLoaderGw() { 60 | super(null); 61 | } 62 | 63 | @Override 64 | public Class findClass(String name) throws ClassNotFoundException { 65 | try{ 66 | InputStream classData = ClassLoader.getSystemResourceAsStream(name.replace('.', '/') + ".class"); 67 | if(classData == null) { 68 | throw new ClassNotFoundException("class " + name + " is not findable"); 69 | } 70 | byte[] array = bytes(classData); 71 | return defineClass(name, array, 0, array.length); 72 | } catch(IOException ex) { 73 | throw new ClassNotFoundException(name); 74 | } 75 | } 76 | 77 | private static byte[] bytes(InputStream is) throws IOException{ 78 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 79 | int nRead; 80 | byte[] data = new byte[1024]; 81 | while ((nRead = is.read(data, 0, data.length)) != -1) { 82 | buffer.write(data, 0, nRead); 83 | } 84 | buffer.flush(); 85 | return buffer.toByteArray(); 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/TestVoidElements.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow.test; 26 | 27 | import htmlflow.test.views.HtmlVoidElements; 28 | import org.junit.Test; 29 | 30 | import java.util.Iterator; 31 | 32 | import static htmlflow.test.Utils.NEWLINE; 33 | import static org.junit.Assert.assertEquals; 34 | 35 | public class TestVoidElements { 36 | 37 | @Test 38 | public void testStaticViewWithVoidElements() { 39 | String actual = HtmlVoidElements.voidElements.render(); 40 | assertLines("voidElements.html", actual); 41 | } 42 | 43 | private static void assertLines(String pathToExpected, String actual) { 44 | Iterator iter = NEWLINE 45 | .splitAsStream(actual) 46 | .map(String::toLowerCase) 47 | .iterator(); 48 | Utils 49 | .loadLines(pathToExpected) 50 | .map(String::toLowerCase) 51 | .forEach(expected -> { 52 | String line = iter.next(); 53 | assertEquals(expected, line); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/TestWrongUseOfViews.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow.test; 26 | 27 | import htmlflow.HtmlFlow; 28 | import org.junit.Assert; 29 | import org.junit.Test; 30 | 31 | public class TestWrongUseOfViews { 32 | 33 | /** 34 | * A StaticHtml view should not use dynamic(). 35 | * LocalDate is the model type in this test. 36 | */ 37 | @Test(expected = IllegalStateException.class) 38 | public void testWrongUseOfDynamicInStaticHtml(){ 39 | HtmlFlow.doc(System.out) 40 | .html() 41 | .head() 42 | .title().text("Task Details").__() 43 | .dynamic((head, model) -> { 44 | Assert.fail("It should not reach here!"); 45 | }); 46 | } 47 | 48 | /** 49 | * A HtmlDoc cannot be set to thread-safety. 50 | */ 51 | @Test(expected = IllegalStateException.class) 52 | public void testWrongUseOfThreadSafeInViewWithPrintStream(){ 53 | HtmlFlow 54 | .doc(System.out) 55 | .threadSafe(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-16, Miguel Gamboa (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package htmlflow.test; 25 | 26 | import htmlflow.HtmlView; 27 | import org.jsoup.Jsoup; 28 | import org.jsoup.helper.W3CDom; 29 | import org.w3c.dom.Document; 30 | import org.w3c.dom.Element; 31 | 32 | import java.io.BufferedReader; 33 | import java.io.ByteArrayInputStream; 34 | import java.io.ByteArrayOutputStream; 35 | import java.io.Closeable; 36 | import java.io.IOException; 37 | import java.io.InputStream; 38 | import java.io.InputStreamReader; 39 | import java.io.UncheckedIOException; 40 | import java.nio.charset.StandardCharsets; 41 | import java.util.regex.Pattern; 42 | import java.util.stream.Stream; 43 | 44 | /** 45 | * @author Miguel Gamboa 46 | * Created on 22-01-2016. 47 | */ 48 | public class Utils { 49 | 50 | static final Pattern NEWLINE = Pattern.compile("\\n"); 51 | 52 | private Utils() {} 53 | 54 | static Element getRootElement(byte[] input) { 55 | W3CDom w3cDom = new W3CDom(); 56 | Document doc = w3cDom.fromJsoup(Jsoup.parse(new String(input, StandardCharsets.UTF_8))); 57 | return doc.getDocumentElement(); 58 | } 59 | 60 | static Stream htmlWrite(ByteArrayOutputStream mem){ 61 | InputStreamReader actual = new InputStreamReader(new ByteArrayInputStream(mem.toByteArray())); 62 | return new BufferedReader(actual).lines(); 63 | } 64 | 65 | static Stream htmlRender(HtmlView view, T model){ 66 | String html = view.render(model); 67 | return NEWLINE.splitAsStream(html); 68 | } 69 | 70 | public static Stream loadLines(String path) { 71 | try{ 72 | InputStream in = TestDivDetails.class 73 | .getClassLoader() 74 | .getResource(path) 75 | .openStream(); 76 | BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 77 | return reader.lines().onClose(asUncheckedRunnable(reader)); 78 | } catch (IOException e) { 79 | throw new UncheckedIOException(e); 80 | } 81 | } 82 | 83 | /** 84 | * Convert a Closeable to a Runnable by converting checked IOException 85 | * to UncheckedIOException 86 | */ 87 | private static Runnable asUncheckedRunnable(Closeable c) { 88 | return () -> { 89 | try { 90 | c.close(); 91 | } catch (IOException e) { 92 | throw new UncheckedIOException(e); 93 | } 94 | }; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/model/AsyncDynamicModel.java: -------------------------------------------------------------------------------- 1 | package htmlflow.test.model; 2 | 3 | import org.reactivestreams.Publisher; 4 | 5 | import java.util.stream.Stream; 6 | 7 | public class AsyncDynamicModel { 8 | private final String[] titles; 9 | private final String[] studentAndGradesTitles; 10 | private final Publisher students; 11 | private final Stream withGradeStream; 12 | 13 | public AsyncDynamicModel(String[] titles, String[] studentAndGradesTitles, Publisher students, Stream withGradeStream) { 14 | this.titles = titles; 15 | this.studentAndGradesTitles = studentAndGradesTitles; 16 | this.students = students; 17 | this.withGradeStream = withGradeStream; 18 | } 19 | 20 | public Publisher getStudents() { 21 | return students; 22 | } 23 | 24 | public Stream getWithGradeStream() { 25 | return withGradeStream; 26 | } 27 | 28 | public String[] getTitles() { 29 | return titles; 30 | } 31 | 32 | public String[] getStudentAndGradesTitles() { 33 | return studentAndGradesTitles; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/model/AsyncModel.java: -------------------------------------------------------------------------------- 1 | package htmlflow.test.model; 2 | 3 | import org.reactivestreams.Publisher; 4 | 5 | public class AsyncModel { 6 | 7 | public final Publisher titles; 8 | public final Publisher items; 9 | 10 | public AsyncModel(Publisher titles, Publisher items) { 11 | this.titles = titles; 12 | this.items = items; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/model/Priority.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-16, Miguel Gamboa (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package htmlflow.test.model; 25 | 26 | /** 27 | * @author Miguel Gamboa 28 | */ 29 | public enum Priority { 30 | LOW, NORMAL, HIGH 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/model/Status.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-16, Miguel Gamboa (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package htmlflow.test.model; 25 | 26 | /** 27 | * @author Miguel Gamboa 28 | */ 29 | public enum Status { 30 | UNSTARTED, PROGRESS, COMPLETED, DEFERRED 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/model/Stock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow.test.model; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | public class Stock { 31 | 32 | private int index; 33 | 34 | private String name; 35 | 36 | private String name2; 37 | 38 | private String url; 39 | 40 | private String symbol; 41 | 42 | private double price; 43 | 44 | private double change; 45 | 46 | private double ratio; 47 | 48 | public Stock(int index, String name, String name2, String url, String symbol, double price, double change, double ratio) { 49 | this.index = index; 50 | this.name = name; 51 | this.name2 = name2; 52 | this.url = url; 53 | this.symbol = symbol; 54 | this.price = price; 55 | this.change = change; 56 | this.ratio = ratio; 57 | } 58 | 59 | public int getIndex() { 60 | return index; 61 | } 62 | 63 | public String getName() { 64 | return this.name; 65 | } 66 | 67 | public String getName2() { 68 | return this.name2; 69 | } 70 | 71 | public String getUrl() { 72 | return this.url; 73 | } 74 | 75 | public String getSymbol() { 76 | return this.symbol; 77 | } 78 | 79 | public double getPrice() { 80 | return this.price; 81 | } 82 | 83 | public double getChange() { 84 | return this.change; 85 | } 86 | 87 | public double getRatio() { 88 | return this.ratio; 89 | } 90 | 91 | public static List dummy3Items() { 92 | List items = new ArrayList(); 93 | items.add(new Stock(1, "Adobe Systems", "Adobe Systems Inc.", "http://www.adobe.com", "ADBE", 39.26, 0.13, 0.33)); 94 | items.add(new Stock(2, "Advanced Micro Devices", "Advanced Micro Devices Inc.", "http://www.amd.com", "AMD", 95 | 16.22, 0.17, 1.06)); 96 | items.add(new Stock(3, "Amazon.com", "Amazon.com Inc", "http://www.amazon.com", "AMZN", 36.85, -0.23, -0.62)); 97 | return items; 98 | } 99 | 100 | public static List other3Items() { 101 | List items = new ArrayList(); 102 | items.add(new Stock(8, "Dell", "Dell Corp.", "http://www.dell.com/", "DELL", 23.73, -0.42, -1.74)); 103 | items.add(new Stock(9, "eBay", "eBay Inc.", "http://www.ebay.com", "EBAY", 31.65, -0.8, -2.47)); 104 | items.add(new Stock(10, "Google", "Google Inc.", "http://www.google.com", "GOOG", 495.84, 7.75, 1.59)); 105 | return items; 106 | } 107 | 108 | public static List dummy5Items() { 109 | List items = new ArrayList(); 110 | items.add(new Stock(7, "Cisco Systems", "Cisco Systems Inc.", "http://www.cisco.com", "CSCO", 26.35, 0.13, 0.5)); 111 | items.add(new Stock(8, "Dell", "Dell Corp.", "http://www.dell.com/", "DELL", 23.73, -0.42, -1.74)); 112 | items.add(new Stock(9, "eBay", "eBay Inc.", "http://www.ebay.com", "EBAY", 31.65, -0.8, -2.47)); 113 | items.add(new Stock(10, "Google", "Google Inc.", "http://www.google.com", "GOOG", 495.84, 7.75, 1.59)); 114 | items.add(new Stock(11, "Hewlett-Packard", "Hewlett-Packard Co.", "http://www.hp.com", "HPQ", 41.69, -0.02, -0.05)); 115 | return items; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/model/Student.java: -------------------------------------------------------------------------------- 1 | package htmlflow.test.model; 2 | 3 | /** 4 | * @author Pedro Fialho 5 | **/ 6 | public class Student { 7 | private final long nr; 8 | private final String name; 9 | 10 | public Student(long nr, String name) { 11 | this.nr = nr; 12 | this.name = name; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return String.format("Student nr %d with name %s",nr, name); 18 | } 19 | 20 | public long getNr() { 21 | return nr; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/model/StudentWithGrade.java: -------------------------------------------------------------------------------- 1 | package htmlflow.test.model; 2 | 3 | public class StudentWithGrade { 4 | private final String name; 5 | private final double grade; 6 | 7 | public StudentWithGrade(String name, double grade) { 8 | this.name = name; 9 | this.grade = grade; 10 | } 11 | 12 | public String getName() { 13 | return name; 14 | } 15 | 16 | public double getGrade() { 17 | return grade; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/model/Task.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-16, Miguel Gamboa (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package htmlflow.test.model; 25 | 26 | /** 27 | * @author Miguel Gamboa 28 | */ 29 | public class Task { 30 | 31 | private final int id; 32 | private final String title; 33 | private final String description; 34 | private final Priority priority; 35 | private Status status; 36 | 37 | public Task(String title, String description, Priority priority) { 38 | this.title = title; 39 | this.description = description; 40 | this.priority = priority; 41 | this.id = hashCode(); 42 | } 43 | 44 | public Task(int id, String title, String description, Priority priority) { 45 | this.title = title; 46 | this.description = description; 47 | this.priority = priority; 48 | this.id = id; 49 | } 50 | public Task(String title, String description, Priority priority, Status status) { 51 | this(title,description,priority); 52 | this.status = status; 53 | } 54 | 55 | public Task(int id, String title, String description, Priority priority, Status status) { 56 | this(id, title, description, priority); 57 | this.status = status; 58 | } 59 | 60 | public int getId() { 61 | return id; 62 | } 63 | 64 | public String getTitle() { 65 | return title; 66 | } 67 | 68 | public String getDescription() { 69 | return description; 70 | } 71 | 72 | public Priority getPriority() { 73 | return priority; 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return "Task [id=" + id + ", title=" + title + ", description=" 79 | + description + ", priority=" + priority + ", status=" + status 80 | + "]"; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/model/TaskDelayed.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-23, Miguel Gamboa (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package htmlflow.test.model; 25 | 26 | /** 27 | * @author Miguel Gamboa 28 | */ 29 | public class TaskDelayed extends Task { 30 | 31 | public TaskDelayed(int id, String title, String description, Priority priority) { 32 | super(id, title, description, priority); 33 | } 34 | 35 | @Override public String getTitle() { 36 | sleep(10); 37 | return super.getTitle(); 38 | } 39 | 40 | @Override public String getDescription() { 41 | sleep(10); 42 | return super.getDescription(); 43 | } 44 | 45 | @Override public Priority getPriority() { 46 | sleep(10); 47 | return super.getPriority(); 48 | } 49 | 50 | private static void sleep(int delay) { 51 | try { 52 | Thread.sleep(delay); 53 | } catch (InterruptedException e) { 54 | throw new RuntimeException(e); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/model/Track.java: -------------------------------------------------------------------------------- 1 | package htmlflow.test.model; 2 | 3 | import java.time.LocalDate; 4 | 5 | public class Track { 6 | final String artist; 7 | final String name; 8 | final String url; 9 | final LocalDate diedDate; 10 | final int listeners; 11 | 12 | public Track(String name) { 13 | this(null, name, null, 0, null); 14 | } 15 | 16 | public Track(String band, String name) { 17 | this(band, name, null, 0, null); 18 | } 19 | 20 | public Track(String band, String name, LocalDate died) { 21 | this(band, name, null, 0, died); 22 | } 23 | 24 | public Track(String band, String name, String url, int listeners, LocalDate died) { 25 | this.artist = band; 26 | this.name = name; 27 | this.url = url; 28 | this.listeners = listeners; 29 | this.diedDate = died; 30 | } 31 | 32 | public String getArtist() { 33 | return artist; 34 | } 35 | 36 | public LocalDate getDiedDate() { 37 | return diedDate; 38 | } 39 | 40 | public String getName() { 41 | return name; 42 | } 43 | 44 | public String getUrl() { 45 | return url; 46 | } 47 | 48 | public int getListeners() { 49 | return listeners; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/views/HtmlCustomElements.java: -------------------------------------------------------------------------------- 1 | package htmlflow.test.views; 2 | 3 | import htmlflow.HtmlFlow; 4 | import htmlflow.HtmlView; 5 | 6 | public class HtmlCustomElements { 7 | public static HtmlView customElements = HtmlFlow.view(view -> view 8 | .html() 9 | .head() 10 | .script() 11 | .attrSrc("alert.js") // Link to alert-info definition 12 | .attrDefer(true) 13 | .__() 14 | .__() 15 | .body() 16 | .div() 17 | .attrClass("container") 18 | .p().text("Testing custom elements!").__() 19 | .custom("alert-info") // alert-info should be stored in the new Element and accessible to the Visitor. 20 | .addAttr("title", "Information") 21 | .addAttr("message", "This is a message for a custom element") 22 | .addAttr("kind", "success") 23 | .ul() 24 | .li().text("For any reason we could even include other elements.").__() 25 | .__() // ul 26 | .__() // alert-info 27 | .__() // div 28 | .__() // body 29 | .__() //html 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/views/HtmlDynamic.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow.test.views; 26 | 27 | import htmlflow.HtmlFlow; 28 | import htmlflow.HtmlPage; 29 | import htmlflow.HtmlView; 30 | import htmlflow.test.model.Stock; 31 | import org.xmlet.htmlapifaster.EnumHttpEquivType; 32 | import org.xmlet.htmlapifaster.EnumMediaType; 33 | import org.xmlet.htmlapifaster.EnumRelType; 34 | import org.xmlet.htmlapifaster.EnumTypeContentType; 35 | import org.xmlet.htmlapifaster.EnumTypeScriptType; 36 | import org.xmlet.htmlapifaster.Tbody; 37 | 38 | public class HtmlDynamic { 39 | 40 | public static HtmlView stocksViewOk = HtmlFlow.view(HtmlDynamic::templateStocksOk); 41 | 42 | private static void templateStocksOk(HtmlPage page) { 43 | page 44 | .html() 45 | .head() 46 | .title().text("Stock Prices").__() 47 | .meta() 48 | .attrHttpEquiv(EnumHttpEquivType.CONTENT_TYPE) 49 | .attrContent("text/html; charset=UTF-8") 50 | .__() 51 | .meta() 52 | .addAttr("http-equiv", "Content-Style-Type") 53 | .attrContent("text/CSS") 54 | .__() 55 | .meta() 56 | .addAttr("http-equiv", "Content-Script-Type") 57 | .attrContent("text/javascript") 58 | .__() 59 | .link() 60 | .addAttr("rel", "shortcut icon") 61 | .attrHref("/images/favicon.ico") 62 | .__() 63 | .link() 64 | .attrRel(EnumRelType.STYLESHEET) 65 | .attrType(EnumTypeContentType.TEXT_CSS) 66 | .attrHref("/CSS/style.CSS") 67 | .attrMedia(EnumMediaType.ALL) 68 | .__() 69 | .script() 70 | .attrType(EnumTypeScriptType.TEXT_JAVASCRIPT) 71 | .attrSrc("/js/util.js") 72 | .__() 73 | .__() // head 74 | .body() 75 | .h1().text("Stock Prices").__() 76 | .table() 77 | .thead() 78 | .tr() 79 | .th().text("#").__() 80 | .th().text("symbol").__() 81 | .th().text("name").__() 82 | .th().text("price").__() 83 | .th().text("change").__() 84 | .th().text("ratio").__() 85 | .__() // tr 86 | .__() // thead 87 | .tbody() 88 | .>dynamic((tbody, stocks) -> stocks.forEach(stock -> tableRowView(tbody, stock))) 89 | .__() // tbody 90 | .__() // table 91 | .__() // body 92 | .__(); // html 93 | } 94 | 95 | static final void tableRowView(Tbody tbody, Stock stock) { 96 | tbody 97 | .tr().attrClass(stock.getIndex() % 2 == 0 ? "even" : "odd") 98 | .td().text(stock.getIndex()).__() 99 | .td() 100 | .a().attrHref("/stocks/" + stock.getSymbol()).text(stock.getSymbol()).__() 101 | .__() 102 | .td() 103 | .a().attrHref(stock.getUrl()).text(stock.getName()).__() 104 | .__() 105 | .td().strong().text(stock.getPrice()).__().__() 106 | .td() 107 | .of(td -> { 108 | double change = stock.getChange(); 109 | if (change < 0) { 110 | td.attrClass("minus"); 111 | } 112 | td.text(change); 113 | }) 114 | .__() 115 | .td() 116 | .of(td -> { 117 | double ratio = stock.getRatio(); 118 | if (ratio < 0) { 119 | td.attrClass("minus"); 120 | } 121 | td.text(ratio); 122 | }) 123 | .__() 124 | .__(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/views/HtmlDynamicChainTwiceOnTopgenius.java: -------------------------------------------------------------------------------- 1 | package htmlflow.test.views; 2 | 3 | import htmlflow.HtmlFlow; 4 | import htmlflow.HtmlPage; 5 | import htmlflow.HtmlView; 6 | import htmlflow.test.model.Track; 7 | import org.xmlet.htmlapifaster.EnumRelType; 8 | 9 | import java.util.stream.Stream; 10 | 11 | public class HtmlDynamicChainTwiceOnTopgenius { 12 | 13 | public static final HtmlView toptracksOkOfWithDynamic = HtmlFlow 14 | .view(HtmlDynamicChainTwiceOnTopgenius::toptracksTemplateOfAndDynamic); 15 | 16 | public static void toptracksTemplateDynamicTwice(HtmlPage view) { 17 | view 18 | .html() 19 | .head() 20 | .link() 21 | .attrRel(EnumRelType.STYLESHEET) 22 | .attrHref("/stylesheets/bootstrap.min.css") 23 | .__() 24 | .title().text("TopGenius.eu").__() 25 | .__() 26 | .body() 27 | .div() 28 | .attrClass("container") 29 | .table() 30 | .attrClass("table") 31 | .tr() 32 | .th().text("Rank").__() 33 | .th().text("Track").__() 34 | .th().text("Listeners").__() 35 | .__() 36 | .>dynamic((table, tracks) -> { 37 | int[] count = {1}; 38 | tracks.forEach(track -> 39 | table 40 | .tr() 41 | .td().dynamic((td, obj) -> td.text(count[0]++)).__() 42 | .td() 43 | .dynamic((td, trk) -> td 44 | .a() 45 | .attrHref(track.getUrl()) 46 | .attrTarget("_blank") 47 | .text(track.getName()) 48 | .__()) 49 | .__() 50 | .td().dynamic((td, trk) -> td.text(track.getListeners())).__() 51 | .__() 52 | ); 53 | }) 54 | .__() 55 | .__() 56 | .__() 57 | .__(); 58 | } 59 | static void toptracksTemplateOfAndDynamic(HtmlPage view) { 60 | view 61 | .html() 62 | .head() 63 | .link() 64 | .attrRel(EnumRelType.STYLESHEET) 65 | .attrHref("/stylesheets/bootstrap.min.css") 66 | .__() 67 | .title().text("TopGenius.eu").__() 68 | .__() 69 | .body() 70 | .div() 71 | .attrClass("container") 72 | .table() 73 | .attrClass("table") 74 | .tr() 75 | .th().text("Rank").__() 76 | .th().text("Track").__() 77 | .th().text("Listeners").__() 78 | .__() 79 | .>dynamic((table, tracks) -> { 80 | int[] count = {1}; 81 | tracks.forEach(track -> 82 | table 83 | .tr() 84 | .td().text(count[0]++).__() 85 | .td() 86 | .a() 87 | .attrHref(track.getUrl()) 88 | .attrTarget("_blank") 89 | .text(track.getName()) 90 | .__() 91 | .__() 92 | .td().text(track.getListeners()).__() 93 | .__() 94 | ); 95 | }) 96 | .__() 97 | .__() 98 | .__() 99 | .__(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/views/HtmlLists.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow.test.views; 26 | 27 | import htmlflow.HtmlFlow; 28 | import htmlflow.HtmlPage; 29 | import htmlflow.HtmlView; 30 | import htmlflow.test.model.Task; 31 | import org.xmlet.htmlapifaster.EnumEnctypeType; 32 | import org.xmlet.htmlapifaster.EnumMethodType; 33 | import org.xmlet.htmlapifaster.EnumRelType; 34 | import org.xmlet.htmlapifaster.EnumTypeContentType; 35 | import org.xmlet.htmlapifaster.EnumTypeScriptType; 36 | 37 | public class HtmlLists { 38 | public static final String DIV_CLASS = "divClass"; 39 | public static final String DIV_ID = "divId"; 40 | 41 | public static HtmlPage taskView (Appendable out) { 42 | return HtmlFlow.doc(out) 43 | .html() 44 | .head() 45 | .script() 46 | .attrType(EnumTypeScriptType.TEXT_JAVASCRIPT) 47 | .attrSrc("test.css") 48 | .__() //script 49 | .__() // head 50 | .body() 51 | .div() 52 | .comment("A simple dummy comment") 53 | .__() //div 54 | .div() 55 | .attrId(DIV_ID) 56 | .attrClass(DIV_CLASS) 57 | .addAttr("toto", "tutu") 58 | .form() 59 | .attrAction("/action.do") 60 | .attrMethod(EnumMethodType.POST) 61 | .attrEnctype(EnumEnctypeType.APPLICATION_X_WWW_FORM_URLENCODED) 62 | .__() //form 63 | .__() //div 64 | .__() //body 65 | .__(); //html 66 | } 67 | 68 | public static HtmlView viewDetails = HtmlFlow.view(view -> view 69 | .html() 70 | .head() 71 | .title().text("Task Details").__() 72 | .link() 73 | .attrRel(EnumRelType.STYLESHEET) 74 | .attrType(EnumTypeContentType.TEXT_CSS) 75 | .attrHref("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css") 76 | .__() //link 77 | .__() //head 78 | .body() 79 | .attrClass("container") 80 | .h1().text("Task Details").__() 81 | .hr().__() 82 | .div().text("Title: ISEL MPD project") 83 | .br().__() 84 | .text("Description: A Java library for serializing objects in HTML.") 85 | .br().__() 86 | .text("Priority: HIGH") 87 | .__() //div 88 | .__() //body 89 | .__() //html 90 | ); 91 | public static void taskDetailsTemplate(HtmlPage view) { 92 | view 93 | .html() 94 | .head() 95 | .title().text("Task Details").__() 96 | .__() //head 97 | .body() 98 | .dynamic((body, task) -> body.text("Title:").text(task.getTitle())) 99 | .br().__() 100 | .dynamic((body, task) -> body.text("Description:").text(task.getDescription())) 101 | .br().__() 102 | .dynamic((body, task) -> body.text("Priority:").text(task.getPriority())) 103 | .__() //body 104 | .__(); // html 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/views/HtmlVoidElements.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package htmlflow.test.views; 26 | 27 | import htmlflow.HtmlFlow; 28 | import htmlflow.HtmlView; 29 | import org.xmlet.htmlapifaster.EnumShapeType; 30 | import org.xmlet.htmlapifaster.EnumTypeInputType; 31 | 32 | public class HtmlVoidElements { 33 | public static HtmlView voidElements = HtmlFlow.view(view -> view 34 | .html() 35 | .head() 36 | .title() 37 | .text("Html View with somid elements") 38 | .__() 39 | .base() 40 | .attrHref("http://www.example.com/page.html") 41 | .__() 42 | .__() 43 | .body() 44 | .embed() 45 | .attrType("video/webm") 46 | .attrSrc("/media/stream_of_water_audioless.webm") 47 | .attrWidth("300") 48 | .attrHeight("200") 49 | .__() 50 | .input() 51 | .attrType(EnumTypeInputType.TEXT) 52 | .attrId("display-name") 53 | .attrPattern("[A-Za-z\\s]+") 54 | .attrMaxlength(5L) 55 | .attrValue("Aa") 56 | .attrRequired(true) 57 | .__() 58 | .table() 59 | .colgroup() 60 | .col().__() 61 | .col().attrSpan(2L).attrClass("batman").__() 62 | .col().attrSpan(2L).attrClass("flash").__() 63 | .__() 64 | .__() 65 | .video() 66 | .source() 67 | .attrSrc("/media/examples/stream_of_water.webm") 68 | .attrType("video/webm") 69 | .__() 70 | .__() 71 | .area() 72 | .attrShape(EnumShapeType.CIRCLE) 73 | .attrCoords("130,136,60") 74 | .attrHref("https://developer.mozilla.org/") 75 | .attrTarget("_blank") 76 | .attrAlt("MDN") 77 | .__() 78 | .object() 79 | .param() 80 | .attrName("dummy") 81 | .__() 82 | .__() 83 | .__() 84 | .__() 85 | ); 86 | } -------------------------------------------------------------------------------- /src/test/java/htmlflow/test/views/HtmlWithoutIndentation.java: -------------------------------------------------------------------------------- 1 | package htmlflow.test.views; 2 | 3 | import htmlflow.HtmlFlow; 4 | import htmlflow.HtmlPage; 5 | 6 | import java.io.PrintStream; 7 | 8 | public class HtmlWithoutIndentation { 9 | 10 | public static void bodyDivP(StringBuilder out) { 11 | HtmlFlow 12 | .doc(out) 13 | .setIndented(false) 14 | .html() 15 | .body() 16 | .div() 17 | .p() 18 | .text("Some dummy text!") 19 | .__() // p 20 | .__() // div 21 | .__() // body 22 | .__(); // html 23 | } 24 | 25 | public static void bodyDivPtemplate(HtmlPage view) { 26 | view 27 | .html() 28 | .body() 29 | .div() 30 | .p() 31 | .text("Some dummy text!") 32 | .__() // p 33 | .__() // div 34 | .__() // body 35 | .__(); // html 36 | } 37 | 38 | public static void bodyPre(StringBuilder out) { 39 | HtmlFlow 40 | .doc(out) 41 | .setIndented(false) 42 | .html() 43 | .body() 44 | .pre() 45 | .text("Some text") 46 | .__() // pre 47 | .__() // body 48 | .__(); // html 49 | } 50 | 51 | public static void hotBodyDivP(PrintStream out) { 52 | HtmlFlow 53 | .doc(out) 54 | .setIndented(false) 55 | .html() 56 | .body() 57 | .div() 58 | .p() 59 | .text("Some dummy text!") 60 | .__() // p 61 | .__() // div 62 | .__() // body 63 | .__(); // html 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/htmlflow/visitor/TagsTest.java: -------------------------------------------------------------------------------- 1 | package htmlflow.visitor; 2 | 3 | import htmlflow.exceptions.HtmlFlowAppendException; 4 | import org.junit.jupiter.api.DisplayNameGeneration; 5 | import org.junit.jupiter.api.DisplayNameGenerator; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | import org.mockito.Mock; 9 | import org.mockito.junit.jupiter.MockitoExtension; 10 | 11 | import java.io.IOException; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertThrowsExactly; 14 | import static org.mockito.ArgumentMatchers.anyChar; 15 | import static org.mockito.ArgumentMatchers.anyString; 16 | import static org.mockito.Mockito.when; 17 | 18 | @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) 19 | @ExtendWith(MockitoExtension.class) 20 | class TagsTest { 21 | 22 | @Mock 23 | private Appendable appendable; 24 | 25 | @Test 26 | void given_exception_on_append_on_begin_tag_should_rethrow_htmlFlow_exception() throws IOException { 27 | 28 | when(appendable.append(anyChar())).thenThrow(new IOException("oops")); 29 | 30 | assertThrowsExactly(HtmlFlowAppendException.class, () -> Tags.beginTag(appendable, "elem")); 31 | } 32 | 33 | @Test 34 | void given_exception_on_append_on_end_tag_should_rethrow_htmlFlow_exception() throws IOException { 35 | 36 | when(appendable.append(anyString())).thenThrow(new IOException("oops")); 37 | 38 | assertThrowsExactly(HtmlFlowAppendException.class, () -> Tags.endTag(appendable, "elem")); 39 | } 40 | 41 | @Test 42 | void given_exception_on_append_on_add_attribute_should_rethrow_htmlFlow_exception() throws IOException { 43 | 44 | when(appendable.append(anyChar())).thenThrow(new IOException("oops")); 45 | 46 | assertThrowsExactly(HtmlFlowAppendException.class, () -> Tags.addAttribute(appendable, "elem", "value")); 47 | } 48 | 49 | @Test 50 | void given_exception_on_append_on_add_comment_should_rethrow_htmlFlow_exception() throws IOException { 51 | 52 | when(appendable.append(anyString())).thenThrow(new IOException("oops")); 53 | 54 | assertThrowsExactly(HtmlFlowAppendException.class, () -> Tags.addComment(appendable, "elem")); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/kotlin/htmlflow/test/TestKotlinSuspend.kt: -------------------------------------------------------------------------------- 1 | package htmlflow.test 2 | 3 | import htmlflow.* 4 | import kotlinx.coroutines.async 5 | import kotlinx.coroutines.delay 6 | import kotlinx.coroutines.future.await 7 | import kotlinx.coroutines.runBlocking 8 | import org.junit.Test 9 | import java.util.concurrent.CompletableFuture 10 | import java.util.concurrent.CompletableFuture.completedFuture 11 | import kotlin.test.assertEquals 12 | 13 | class Task( 14 | val title: String?, 15 | val description: String, 16 | val priority: String 17 | ) 18 | 19 | class TestKotlinSuspend { 20 | 21 | @Test(expected = IllegalStateException::class) 22 | fun doc_with_illegal_use_of_suspending() { 23 | HtmlFlow.doc(System.out) 24 | .html().body().div() 25 | .suspending { model: String -> p().text(model).l.l} 26 | .l.l.l 27 | } 28 | @Test(expected = IllegalStateException::class) 29 | fun view_with_illegal_use_of_suspending() { 30 | HtmlFlow.view{ page -> page 31 | .html().body().div() 32 | .of { div -> 33 | runBlocking { 34 | div.suspending { -> div.p().text("test").l.l } 35 | } 36 | } 37 | .l.l.l 38 | } 39 | } 40 | @Test 41 | fun doc_with_suspend_function_awaiting() { 42 | val out = StringBuilder() 43 | runBlocking { 44 | val swim = completedFuture(Task("Swim", "Sea open water", "High")) 45 | async { 46 | taskDocTableSuspendAwait(out, swim) // This Doc includes a suspension of 1000 ms 47 | } 48 | delay(100) // Gives chance to former doc render to the suspension point. 49 | assertEquals(expectedHtmlTablePart1, out.toString()) 50 | } 51 | // Check the final document after completion of all tasks 52 | assertEquals(expectedHtmlTable, out.toString()) 53 | } 54 | @Test 55 | fun view_with_suspend_function_awaiting() = runBlocking { 56 | val out = StringBuilder() 57 | val swim = completedFuture(Task("Swim", "Sea open water", "High")) 58 | taskViewTableSuspendAwait.write(out, swim) 59 | assertEquals(expectedHtmlTable, out.toString()) 60 | } 61 | } 62 | 63 | private val taskViewTableSuspendAwait: HtmlViewSuspend> = viewSuspend { 64 | html() 65 | .head().title().text("Dummy Table").l 66 | .l 67 | .body() 68 | .h1().text("Dummy Table").l 69 | .hr().l 70 | .div() 71 | .table() 72 | .tr() 73 | .th().text("Title").l 74 | .th().text("Description").l 75 | .th().text("Priority").l 76 | .l // tr 77 | .suspending { cf: CompletableFuture -> 78 | val task = cf.await() 79 | delay(1000) 80 | tr() 81 | .td().text(task.title).l 82 | .td().text(task.description).l 83 | .td().text(task.priority).l 84 | .l // tr 85 | 86 | } 87 | .l 88 | .l 89 | .l 90 | .l 91 | } 92 | 93 | private suspend fun taskDocTableSuspendAwait(out: Appendable, cf: CompletableFuture) { 94 | HtmlFlow 95 | .doc(out) 96 | .html() 97 | .head().title().text("Dummy Table").l 98 | .l 99 | .body() 100 | .h1().text("Dummy Table").l 101 | .hr().l 102 | .div() 103 | .table() 104 | .tr() 105 | .th().text("Title").l 106 | .th().text("Description").l 107 | .th().text("Priority").l 108 | .l // tr 109 | .suspending { -> 110 | val task = cf.await() 111 | delay(1000) 112 | tr() 113 | .td().text(task.title).l 114 | .td().text(task.description).l 115 | .td().text(task.priority).l 116 | .l // tr 117 | 118 | } 119 | .l 120 | .l 121 | .l 122 | .l 123 | } 124 | 125 | private val expectedHtmlTable = """ 126 | 127 | 128 | 129 | 130 | Dummy Table 131 | 132 | 133 | 134 |

135 | Dummy Table 136 |

137 |
138 |
139 | 140 | 141 | 144 | 147 | 150 | 151 | 152 | 155 | 158 | 161 | 162 |
142 | Title 143 | 145 | Description 146 | 148 | Priority 149 |
153 | Swim 154 | 156 | Sea open water 157 | 159 | High 160 |
163 |
164 | 165 | 166 | """.trimIndent() 167 | 168 | private val expectedHtmlTablePart1 = """ 169 | 170 | 171 | 172 | 173 | Dummy Table 174 | 175 | 176 | 177 |

178 | Dummy Table 179 |

180 |
181 |
182 | 183 | 184 | 187 | 190 | 193 | 194 | """.trimIndent() 195 | -------------------------------------------------------------------------------- /src/test/kotlin/htmlflow/test/TestSuspendableViewArtist.kt: -------------------------------------------------------------------------------- 1 | package htmlflow.test 2 | 3 | import htmlflow.* 4 | import kotlinx.coroutines.future.await 5 | import kotlinx.coroutines.runBlocking 6 | import org.junit.Test 7 | import reactor.core.publisher.Mono 8 | import java.lang.System.lineSeparator 9 | import java.time.Duration 10 | import java.time.temporal.ChronoUnit 11 | import java.util.concurrent.CompletableFuture 12 | import kotlin.test.assertEquals 13 | 14 | class TestSuspendableViewArtist { 15 | @Test 16 | fun testSuspendableViewArtist() = runBlocking { 17 | val bowie = Artist( 18 | "David Bowie", 19 | MusicBrainz(artist = "David Bowie", 1960, "New York", listOf("Pop Rock")), 20 | SpotifyArtist("David Bowie", 16428657, listOf("Heroes", "Starman", "Rebel Rebel", "Space Oddity", "Let's dance"), listOf()), 21 | ) 22 | val bowieAsync = ArtistAsync( 23 | System.currentTimeMillis(), 24 | bowie.name, 25 | bowie.monoMusicBrainz(), 26 | bowie.monoSpotify(), 27 | ) 28 | val html = htmlFlowArtistSuspendingView.render(bowieAsync) 29 | .split(lineSeparator()) 30 | .iterator() 31 | expectedArtistHtml 32 | .split(lineSeparator()) 33 | .forEach { assertEquals(it, html.next()) } 34 | 35 | } 36 | } 37 | 38 | private val htmlFlowArtistSuspendingView = viewSuspend { 39 | html() 40 | .body() 41 | .h3().dyn { m: ArtistAsync -> text(m.name) } 42 | .l // h3 43 | .h3().text("MusicBrainz info:").l 44 | .ul() 45 | .suspending { m: ArtistAsync -> 46 | val mb = m.musicBrainz.await() 47 | li().raw("Founded: ${mb.year}").l 48 | li().raw("From: ${mb.from}").l 49 | li().raw("Genre: ${mb.genres}").l 50 | } 51 | .l // ul 52 | .p().b().text("Spotify popular tracks:").l 53 | .await { m: ArtistAsync, resume -> m.spotify.thenAccept { 54 | +it.popularSongs.joinToString(", ") 55 | resume() 56 | }} 57 | .l // p 58 | .l // body 59 | .l // html 60 | } 61 | 62 | private class ArtistAsync( 63 | val startTime: Long, 64 | val name: String, 65 | val musicBrainz: CompletableFuture, 66 | val spotify: CompletableFuture, 67 | ) 68 | 69 | private class MusicBrainz( 70 | val artist: String, 71 | val year: Int, 72 | val from: String, 73 | genresList: List) 74 | { 75 | val genres = genresList.joinToString(", ") 76 | } 77 | 78 | private class SpotifyArtist( 79 | val artist: String, 80 | val monthlyListeners: Int, 81 | val popularSongs: List, 82 | val albums: List) 83 | 84 | 85 | private data class Artist( 86 | val name: String, 87 | private val musicBrainz: MusicBrainz, 88 | private val spotify: SpotifyArtist, 89 | ) { 90 | companion object { 91 | var timeout: Long = 100 92 | } 93 | 94 | fun monoMusicBrainz() = Mono 95 | .fromSupplier { musicBrainz } 96 | .delayElement(Duration.of(timeout, ChronoUnit.MILLIS)) 97 | .toFuture() 98 | 99 | 100 | fun monoSpotify() = Mono 101 | .fromSupplier { spotify } 102 | .delayElement(Duration.of(timeout + 1000, ChronoUnit.MILLIS)) 103 | .toFuture() 104 | } 105 | 106 | private const val expectedArtistHtml = """ 107 | 108 | 109 |

110 | David Bowie 111 |

112 |

113 | MusicBrainz info: 114 |

115 |
    116 |
  • 117 | Founded: 1960 118 |
  • 119 |
  • 120 | From: New York 121 |
  • 122 |
  • 123 | Genre: Pop Rock 124 |
  • 125 |
126 |

127 | 128 | Spotify popular tracks: 129 | 130 | Heroes, Starman, Rebel Rebel, Space Oddity, Let's dance 131 |

132 | 133 | """ -------------------------------------------------------------------------------- /src/test/kotlin/htmlflow/test/TestSuspendableViewInConcurInMultpleThreads.kt: -------------------------------------------------------------------------------- 1 | package htmlflow.test 2 | 3 | import htmlflow.* 4 | import kotlinx.coroutines.Job 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.runBlocking 7 | import org.junit.Assert 8 | import org.junit.jupiter.api.Test 9 | import org.xmlet.htmlapifaster.Div 10 | import org.xmlet.htmlapifaster.Span 11 | 12 | class TestSuspendableViewInConcurInMultpleThreads { 13 | @Test 14 | @Throws(InterruptedException::class) 15 | fun testMultipleThreadsInViewAsync() { 16 | val view = viewSuspend { template() }.threadSafe() 17 | checkRender { view.render() } 18 | } 19 | } 20 | 21 | private fun checkRender(render: suspend () -> String) { 22 | // println("start"); 23 | val threadCount = 50 24 | // AtomicInteger left = new AtomicInteger(threadCount); 25 | val thread = arrayOfNulls(threadCount) 26 | val html = arrayOfNulls(threadCount) 27 | runBlocking { 28 | for (i in 0 until threadCount) { 29 | thread[i] = launch { 30 | try { 31 | html[i] = render() 32 | // println("Thread " + threadNumber + " exited, remaining: " + left.decrementAndGet()); 33 | } catch (e: Exception) { 34 | e.printStackTrace() 35 | } 36 | } 37 | } 38 | for (i in 0 until threadCount) { 39 | thread[i]?.join() 40 | Assert.assertEquals(TestAsyncViewInConcurInMultpleThreads.EXPECTED_HTML, html[i]) 41 | } 42 | } 43 | // println("end"); 44 | } 45 | 46 | private fun HtmlPage.template() { 47 | div().span().of { span: Span> -> 48 | try { 49 | Thread.sleep(1) 50 | } catch (e: InterruptedException) { 51 | e.printStackTrace() 52 | } 53 | span.a().attrHref("link").text("text").l 54 | .of { s: Span> -> 55 | try { 56 | Thread.sleep(1) 57 | } catch (e: InterruptedException) { 58 | e.printStackTrace() 59 | } 60 | s.a().attrHref("link2").text("text2").l 61 | } 62 | }.l.l 63 | } 64 | 65 | 66 | private const val expectedHtml = """
67 | 68 | 69 | text 70 | 71 | 72 | text2 73 | 74 | 75 |
""" -------------------------------------------------------------------------------- /src/test/kotlin/htmlflow/test/TestSuspendableViewInConcurrency.kt: -------------------------------------------------------------------------------- 1 | package htmlflow.test 2 | 3 | import htmlflow.test.model.AsyncModel 4 | import htmlflow.test.model.Student 5 | import htmlflow.viewSuspend 6 | import kotlinx.coroutines.GlobalScope 7 | import kotlinx.coroutines.async 8 | import kotlinx.coroutines.runBlocking 9 | import org.junit.jupiter.api.Assertions 10 | import org.junit.jupiter.api.Test 11 | import org.reactivestreams.Publisher 12 | import reactor.core.publisher.Flux 13 | import java.time.Duration 14 | import java.util.stream.Collectors 15 | import java.util.stream.IntStream 16 | 17 | class TestSuspendableViewInConcurrency { 18 | val NR_OF_TASKS = Runtime.getRuntime().availableProcessors() 19 | val studentFlux: Publisher = Flux.range(1, 5) 20 | .delayElements(Duration.ofMillis(10)) 21 | .map { nr: Int -> 22 | Student( 23 | nr.toLong(), 24 | TestAsyncView.randomNameGenerator(Math.toIntExact(nr.toLong())) 25 | ) 26 | } 27 | val titlesFlux: Publisher = Flux.fromArray(arrayOf("Nr", "Name")) 28 | 29 | @Test 30 | fun check_asyncview_processing_in_sequential_tasks_and_unsafe_view() = runBlocking { 31 | /** 32 | * Arrange View 33 | */ 34 | /** 35 | * Arrange View 36 | */ 37 | val view = viewSuspend> { testSuspendingModel() } 38 | .threadUnsafe() 39 | /** 40 | * Act and Assert 41 | * Since Stream is Lazy then there is a vertical processing and a sequential execution between tasks. 42 | */ 43 | /** 44 | * Act and Assert 45 | * Since Stream is Lazy then there is a vertical processing and a sequential execution between tasks. 46 | */ 47 | (0..NR_OF_TASKS) 48 | .map { 49 | view.render(AsyncModel(titlesFlux, studentFlux)) 50 | } 51 | .forEach { assertHtml(it)} 52 | } 53 | 54 | @Test 55 | fun check_asyncview_processing_in_concurrent_tasks_and_parallel_threads() { 56 | /** 57 | * Arrange View 58 | */ 59 | val view = viewSuspend> { testSuspendingModel() } 60 | .threadSafe() 61 | /** 62 | * Act and Assert 63 | * Collects to dispatch resolution through writeAsync() concurrently. 64 | */ 65 | IntStream 66 | .range(0, NR_OF_TASKS) 67 | .parallel() 68 | .mapToObj { GlobalScope.async { 69 | view.render( 70 | AsyncModel(titlesFlux, studentFlux) 71 | ) 72 | } } 73 | .collect(Collectors.toList()) 74 | .forEach{ runBlocking { 75 | assertHtml( 76 | it.await() 77 | ) 78 | }} 79 | } 80 | 81 | private fun assertHtml(html: String) { 82 | val actual = Utils.NEWLINE 83 | .splitAsStream(html) 84 | .iterator() 85 | Utils 86 | .loadLines("asyncTest.html") 87 | .forEach { expected: String? -> 88 | val next = actual.next() 89 | println(next) 90 | Assertions.assertEquals(expected, next) 91 | } 92 | Assertions.assertFalse(actual.hasNext()) 93 | } 94 | } -------------------------------------------------------------------------------- /src/test/kotlin/htmlflow/test/TestSuspendableViewWeather.kt: -------------------------------------------------------------------------------- 1 | package htmlflow.test 2 | 3 | import htmlflow.dyn 4 | import htmlflow.l 5 | import htmlflow.suspending 6 | import htmlflow.viewSuspend 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flow 9 | import kotlinx.coroutines.runBlocking 10 | import org.junit.Test 11 | import org.xmlet.htmlapifaster.EnumBorderType 12 | import kotlin.test.assertEquals 13 | 14 | class TestSuspendableViewWeather { 15 | @Test 16 | fun testSuspendableViewWeather() = runBlocking { 17 | val pt = WeatherRx("Portugal", flow { 18 | emit(Location("Porto", "Light rain", 14)) 19 | emit(Location("Lisbon", "Sunny day", 14)) 20 | emit(Location("Sagres", "Sunny day", 18)) 21 | }) 22 | val html = wxSuspView.render(pt) 23 | assertEquals(expectedWeather, html) 24 | } 25 | } 26 | 27 | private val wxSuspView = viewSuspend { 28 | html() 29 | .head() 30 | .title().dyn { m: WeatherRx -> 31 | text(m.country) 32 | } 33 | .l // title 34 | .l // head 35 | .body() 36 | .table().attrBorder(EnumBorderType._1) 37 | .tr() 38 | .th().text("City").l 39 | .th().text("Temperature").l 40 | .l // tr 41 | .suspending { m: WeatherRx -> m.cities.collect { 42 | tr() 43 | .td().text(it.city).l 44 | .td().text(it.celsius).l 45 | .l // tr 46 | }} 47 | .l // table 48 | .l // body 49 | .l // html 50 | } 51 | 52 | private data class WeatherRx(val country: String, val cities: Flow) 53 | 54 | private const val expectedWeather = """ 55 | 56 | 57 | 58 | Portugal 59 | 60 | 61 | 62 |
185 | Title 186 | 188 | Description 189 | 191 | Priority 192 |
63 | 64 | 67 | 70 | 71 | 72 | 75 | 78 | 79 | 80 | 83 | 86 | 87 | 88 | 91 | 94 | 95 |
65 | City 66 | 68 | Temperature 69 |
73 | Porto 74 | 76 | 14 77 |
81 | Lisbon 82 | 84 | 14 85 |
89 | Sagres 90 | 92 | 18 93 |
96 | 97 | """ -------------------------------------------------------------------------------- /src/test/resources/TaskList.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Task List 6 | 7 | 8 | 9 | 10 |
11 | 12 | HtmlFlow 13 | 14 |

15 | Html page built with HtmlFlow. 16 |

17 |

18 | Task List 19 |

20 |
21 |
22 |
23 | 24 | 25 | 28 | 31 | 34 | 35 | 36 | 37 | 40 | 43 | 46 | 47 | 48 | 51 | 54 | 57 | 58 | 59 | 62 | 65 | 68 | 69 | 70 |
26 | Title 27 | 29 | Description 30 | 32 | Priority 33 |
38 | ISEL MPD project 39 | 41 | A Java library for serializing objects in HTML. 42 | 44 | HIGH 45 |
49 | Special dinner 50 | 52 | Have dinner with someone! 53 | 55 | NORMAL 56 |
60 | Manchester City - Sporting 61 | 63 | 1/8 Final UEFA Europa League. VS. Manchester City - Sporting! 64 | 66 | HIGH 67 |
71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /src/test/resources/asyncTest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

6 | Students from a school board 7 |

8 |
9 |
10 | 11 | 12 | 13 | 16 | 19 | 20 | 21 | 22 | 23 | 26 | 29 | 30 | 31 | 34 | 37 | 38 | 39 | 42 | 45 | 46 | 47 | 50 | 53 | 54 | 55 | 58 | 61 | 62 | 63 |
14 | Nr 15 | 17 | Name 18 |
24 | 1 25 | 27 | Pedro 28 |
32 | 2 33 | 35 | Manuel 36 |
40 | 3 41 | 43 | Maria 44 |
48 | 4 49 | 51 | Clara 52 |
56 | 5 57 | 59 | Rafael 60 |
64 |
65 |
66 |

67 | Best students in school 68 |

69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /src/test/resources/asyncTestSecond.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

6 | Students from a school board 7 |

8 |
9 |
10 | 11 | 12 | 13 | 16 | 19 | 20 | 21 | 22 | 23 | 26 | 29 | 30 | 31 | 34 | 37 | 38 | 39 | 42 | 45 | 46 | 47 | 50 | 53 | 54 | 55 |
14 | Nr 15 | 17 | Name 18 |
24 | 6 25 | 27 | Ze 28 |
32 | 7 33 | 35 | Joan 36 |
40 | 8 41 | 43 | Gui 44 |
48 | 9 49 | 51 | Valery 52 |
56 |
57 |
58 |

59 | Best students in school 60 |

61 |
62 | 63 | 64 | -------------------------------------------------------------------------------- /src/test/resources/asyncTestWithDynamic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

6 | Students from a school and their grades 7 |

8 |
9 |
10 | 11 | 12 | 13 | 16 | 19 | 20 | 21 | 22 | 23 | 26 | 29 | 30 | 31 | 34 | 37 | 38 | 39 | 42 | 45 | 46 | 47 | 50 | 53 | 54 | 55 | 58 | 61 | 62 | 63 |
14 | Nr 15 | 17 | Name 18 |
24 | 1 25 | 27 | Pedro 28 |
32 | 2 33 | 35 | Manuel 36 |
40 | 3 41 | 43 | Maria 44 |
48 | 4 49 | 51 | Clara 52 |
56 | 5 57 | 59 | Rafael 60 |
64 |
65 |
66 | 67 | 68 | 69 | 72 | 75 | 76 | 77 | 78 | 79 | 82 | 85 | 86 | 87 | 90 | 93 | 94 | 95 | 98 | 101 | 102 | 103 | 106 | 109 | 110 | 111 | 114 | 117 | 118 | 119 |
70 | Name 71 | 73 | Grade 74 |
80 | Pedro 81 | 83 | 10.0 84 |
88 | Manuel 89 | 91 | 12.2 92 |
96 | Maria 97 | 99 | 13.0 100 |
104 | Clara 105 | 107 | 14.0 108 |
112 | Rafael 113 | 115 | 9.3 116 |
120 |
121 |
122 |

123 | Best students in school 124 |

125 |
126 | 127 | 128 | -------------------------------------------------------------------------------- /src/test/resources/customElements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 |
9 |

10 | Testing custom elements! 11 |

12 | 13 |
    14 |
  • 15 | For any reason we could even include other elements. 16 |
  • 17 |
18 |
19 |
20 | 21 | -------------------------------------------------------------------------------- /src/test/resources/htmlWithoutIndentationBodyDivP.html: -------------------------------------------------------------------------------- 1 |

Some dummy text!

-------------------------------------------------------------------------------- /src/test/resources/htmlWithoutIndentationBodyPre.html: -------------------------------------------------------------------------------- 1 |
Some text
-------------------------------------------------------------------------------- /src/test/resources/htmlflowSample05ForFlowifier.java: -------------------------------------------------------------------------------- 1 | import htmlflow.*; 2 | import org.xmlet.htmlapifaster.*; 3 | 4 | public class Flowified { 5 | public static void get(StringBuilder out) { 6 | HtmlFlow.doc(out).setIndented(false) 7 | .html() 8 | .head() 9 | .title() 10 | .raw("HtmlFlow") 11 | .__() //title 12 | .__() //head 13 | .body() 14 | .div().attrClass("container") 15 | .h1() 16 | .raw("My first page with HtmlFlow") 17 | .__() //h1 18 | .img().attrSrc("https://avatars1.githubusercontent.com/u/35267172") 19 | .__() //img 20 | .p() 21 | .raw("Typesafe is awesome! :-)") 22 | .__() //p 23 | .__() //div 24 | .__() //body 25 | .__() //html 26 | ; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/resources/logging.properties: -------------------------------------------------------------------------------- 1 | java.util.logging.ConsoleHandler.level=SEVERE -------------------------------------------------------------------------------- /src/test/resources/nestedTable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 22 | 23 |
7 | 8 | 9 | 12 | 13 |
10 | 11 |
14 |
16 | Invoice #: 123 17 |
18 | Created: January 1, 2015 19 |
20 | Due: February 1, 2015 21 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /src/test/resources/partialViewHeader.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | HtmlFlow 5 | 6 |

7 | Html page built with HtmlFlow. 8 |

9 |

10 | Task List 11 |

12 |
-------------------------------------------------------------------------------- /src/test/resources/stocks3items.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Stock Prices 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 |

17 | Stock Prices 18 |

19 | 20 | 21 | 22 | 25 | 28 | 31 | 34 | 37 | 40 | 41 | 42 | 43 | 44 | 47 | 52 | 57 | 62 | 65 | 68 | 69 | 70 | 73 | 78 | 83 | 88 | 91 | 94 | 95 | 96 | 99 | 104 | 109 | 114 | 117 | 120 | 121 | 122 |
23 | # 24 | 26 | symbol 27 | 29 | name 30 | 32 | price 33 | 35 | change 36 | 38 | ratio 39 |
45 | 1 46 | 48 | 49 | ADBE 50 | 51 | 53 | 54 | Adobe Systems 55 | 56 | 58 | 59 | 39.26 60 | 61 | 63 | 0.13 64 | 66 | 0.33 67 |
71 | 2 72 | 74 | 75 | AMD 76 | 77 | 79 | 80 | Advanced Micro Devices 81 | 82 | 84 | 85 | 16.22 86 | 87 | 89 | 0.17 90 | 92 | 1.06 93 |
97 | 3 98 | 100 | 101 | AMZN 102 | 103 | 105 | 106 | Amazon.com 107 | 108 | 110 | 111 | 36.85 112 | 113 | 115 | -0.23 116 | 118 | -0.62 119 |
123 | 124 | 125 | -------------------------------------------------------------------------------- /src/test/resources/stocks3others.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Stock Prices 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 |

17 | Stock Prices 18 |

19 | 20 | 21 | 22 | 25 | 28 | 31 | 34 | 37 | 40 | 41 | 42 | 43 | 44 | 47 | 52 | 57 | 62 | 65 | 68 | 69 | 70 | 73 | 78 | 83 | 88 | 91 | 94 | 95 | 96 | 99 | 104 | 109 | 114 | 117 | 120 | 121 | 122 |
23 | # 24 | 26 | symbol 27 | 29 | name 30 | 32 | price 33 | 35 | change 36 | 38 | ratio 39 |
45 | 8 46 | 48 | 49 | DELL 50 | 51 | 53 | 54 | Dell 55 | 56 | 58 | 59 | 23.73 60 | 61 | 63 | -0.42 64 | 66 | -1.74 67 |
71 | 9 72 | 74 | 75 | EBAY 76 | 77 | 79 | 80 | eBay 81 | 82 | 84 | 85 | 31.65 86 | 87 | 89 | -0.8 90 | 92 | -2.47 93 |
97 | 10 98 | 100 | 101 | GOOG 102 | 103 | 105 | 106 | Google 107 | 108 | 110 | 111 | 495.84 112 | 113 | 115 | 7.75 116 | 118 | 1.59 119 |
123 | 124 | 125 | -------------------------------------------------------------------------------- /src/test/resources/stocks5items.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Stock Prices 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 |

17 | Stock Prices 18 |

19 | 20 | 21 | 22 | 25 | 28 | 31 | 34 | 37 | 40 | 41 | 42 | 43 | 44 | 47 | 52 | 57 | 62 | 65 | 68 | 69 | 70 | 73 | 78 | 83 | 88 | 91 | 94 | 95 | 96 | 99 | 104 | 109 | 114 | 117 | 120 | 121 | 122 | 125 | 130 | 135 | 140 | 143 | 146 | 147 | 148 | 151 | 156 | 161 | 166 | 169 | 172 | 173 | 174 |
23 | # 24 | 26 | symbol 27 | 29 | name 30 | 32 | price 33 | 35 | change 36 | 38 | ratio 39 |
45 | 7 46 | 48 | 49 | CSCO 50 | 51 | 53 | 54 | Cisco Systems 55 | 56 | 58 | 59 | 26.35 60 | 61 | 63 | 0.13 64 | 66 | 0.5 67 |
71 | 8 72 | 74 | 75 | DELL 76 | 77 | 79 | 80 | Dell 81 | 82 | 84 | 85 | 23.73 86 | 87 | 89 | -0.42 90 | 92 | -1.74 93 |
97 | 9 98 | 100 | 101 | EBAY 102 | 103 | 105 | 106 | eBay 107 | 108 | 110 | 111 | 31.65 112 | 113 | 115 | -0.8 116 | 118 | -2.47 119 |
123 | 10 124 | 126 | 127 | GOOG 128 | 129 | 131 | 132 | Google 133 | 134 | 136 | 137 | 495.84 138 | 139 | 141 | 7.75 142 | 144 | 1.59 145 |
149 | 11 150 | 152 | 153 | HPQ 154 | 155 | 157 | 158 | Hewlett-Packard 159 | 160 | 162 | 163 | 41.69 164 | 165 | 167 | -0.02 168 | 170 | -0.05 171 |
175 | 176 | 177 | -------------------------------------------------------------------------------- /src/test/resources/task3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Task Details 6 | 7 | 8 | 9 | Title: 10 | ISEL MPD project 11 |
12 | Description: 13 | A Java library for serializing objects in HTML. 14 |
15 | Priority: 16 | HIGH 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/test/resources/task4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Task Details 6 | 7 | 8 | 9 | Title: 10 | Special dinner 11 |
12 | Description: 13 | Moonlight dinner! 14 |
15 | Priority: 16 | NORMAL 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/test/resources/task5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Task Details 6 | 7 | 8 | 9 | Title: 10 | US Open Final 2018 11 |
12 | Description: 13 | Juan Martin del Potro VS Novak Djokovic 14 |
15 | Priority: 16 | HIGH 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/test/resources/task9.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Task Details 6 | 7 | 8 | 9 | Title: 10 | Web Summit 2018 11 |
12 | Description: 13 | Sir Tim Berners-Lee 14 |
15 | Priority: 16 | HIGH 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/test/resources/testAddPartialAndCheckParentPrintStream.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MyPenguinExample 6 | 7 | 8 | 9 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/test/resources/testIdAndClassAttribute.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 |
9 | 10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /src/test/resources/topgeniusBadAndSit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TopGenius.eu 7 | 8 | 9 | 10 |
11 | 12 | 13 | 16 | 19 | 22 | 23 | 24 | 27 | 32 | 35 | 36 | 37 | 40 | 45 | 48 | 49 |
14 | Rank 15 | 17 | Track 18 | 20 | Listeners 21 |
25 | 1 26 | 28 | 29 | Bad 30 | 31 | 33 | 0 34 |
38 | 2 39 | 41 | 42 | Sit down 43 | 44 | 46 | 0 47 |
50 |
51 | 52 | -------------------------------------------------------------------------------- /src/test/resources/topgeniusSpaceAndPressure.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TopGenius.eu 7 | 8 | 9 | 10 |
11 | 12 | 13 | 16 | 19 | 22 | 23 | 24 | 27 | 32 | 35 | 36 | 37 | 40 | 45 | 48 | 49 |
14 | Rank 15 | 17 | Track 18 | 20 | Listeners 21 |
25 | 1 26 | 28 | 29 | Space Odyssey 30 | 31 | 33 | 0 34 |
38 | 2 39 | 41 | 42 | Under Pressure 43 | 44 | 46 | 0 47 |
50 |
51 | 52 | -------------------------------------------------------------------------------- /src/test/resources/voidElements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | html view with somid elements 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 22 | MDN 23 | 24 | 25 | 26 | 27 | --------------------------------------------------------------------------------