Updated Branches: refs/heads/master f10fc6bd2 -> 1fe4fa73c
TAP5-2071: Tapestry should output valid HTML5 when using the HTML5 doctype in templates Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/1fe4fa73 Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/1fe4fa73 Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/1fe4fa73 Branch: refs/heads/master Commit: 1fe4fa73c4f59b99ce011a9f2119a97728dd77a0 Parents: f10fc6b Author: Ulrich Staerk <[email protected]> Authored: Wed Feb 27 16:21:45 2013 +0100 Committer: Ulrich Staerk <[email protected]> Committed: Wed Feb 27 16:21:45 2013 +0100 ---------------------------------------------------------------------- .../java/org/apache/tapestry5/dom/EndTagStyle.java | 9 +- .../org/apache/tapestry5/dom/Html5MarkupModel.java | 51 ++++++++ .../tapestry5/internal/InternalConstants.java | 7 + .../services/AjaxComponentEventRequestHandler.java | 25 ++-- .../services/AjaxPartialResponseRendererImpl.java | 6 +- .../internal/services/MarkupWriterFactoryImpl.java | 97 ++++++++++++--- .../services/PageResponseRendererImpl.java | 9 +- .../tapestry5/services/MarkupWriterFactory.java | 50 +++++++- .../java/org/apache/tapestry5/dom/DOMTest.java | 36 +++++- 9 files changed, 243 insertions(+), 47 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1fe4fa73/tapestry-core/src/main/java/org/apache/tapestry5/dom/EndTagStyle.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/dom/EndTagStyle.java b/tapestry-core/src/main/java/org/apache/tapestry5/dom/EndTagStyle.java index 7d94233..bb41483 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/dom/EndTagStyle.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/dom/EndTagStyle.java @@ -1,4 +1,4 @@ -// Copyright 2006, 2008, 2011 The Apache Software Foundation +// Copyright 2006, 2008, 2011, 2013 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -29,5 +29,10 @@ public enum EndTagStyle * in XML documents, but {@link org.apache.tapestry5.dom.DefaultMarkupModel} forces most tags to use {@link * #REQUIRE} for semi-obscure browser compatibility issues. */ - ABBREVIATE + ABBREVIATE, + + /** + * No end tags for HTML5 compatibility. + */ + VOID } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1fe4fa73/tapestry-core/src/main/java/org/apache/tapestry5/dom/Html5MarkupModel.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/dom/Html5MarkupModel.java b/tapestry-core/src/main/java/org/apache/tapestry5/dom/Html5MarkupModel.java new file mode 100644 index 0000000..e423c9a --- /dev/null +++ b/tapestry-core/src/main/java/org/apache/tapestry5/dom/Html5MarkupModel.java @@ -0,0 +1,51 @@ +// Copyright 2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package org.apache.tapestry5.dom; + +import java.util.Set; + +import org.apache.tapestry5.ioc.internal.util.CollectionFactory; + +/** + * Implementation of {@link org.apache.tapestry5.dom.MarkupModel} that correctly handles HTML5 void + * elements. It does not support XHTML5. + */ +public class Html5MarkupModel extends AbstractMarkupModel +{ + // http://www.w3.org/TR/html5/syntax.html#void-elements + private final Set<String> VOID_ELEMENTS = CollectionFactory.newSet("area", "base", "br", "col", + "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", + "track", "wbr"); + + public Html5MarkupModel() + { + super(false); + } + + public Html5MarkupModel(boolean useApostropheForAttributes) + { + super(useApostropheForAttributes); + } + + public EndTagStyle getEndTagStyle(String element) + { + return VOID_ELEMENTS.contains(element) ? EndTagStyle.VOID : EndTagStyle.REQUIRE; + } + + public boolean isXML() + { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1fe4fa73/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java index 6bf8f1b..2010a83 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java @@ -14,6 +14,7 @@ package org.apache.tapestry5.internal; +import org.apache.tapestry5.dom.MarkupModel; import org.apache.tapestry5.ioc.util.TimeInterval; import org.apache.tapestry5.services.javascript.JavaScriptStack; @@ -69,6 +70,12 @@ public final class InternalConstants public static final String CONTENT_TYPE_ATTRIBUTE_NAME = "content-type"; public static final String CHARSET_CONTENT_TYPE_PARAMETER = "charset"; + + /** + * As above but to store the name of the page. Necessary for determining the correct + * {@link MarkupModel} for the response. + */ + public static final String PAGE_NAME_ATTRIBUTE_NAME = "page-name"; /** * Required MIME type for JSON responses. If this MIME type is not used, the client-side http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1fe4fa73/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxComponentEventRequestHandler.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxComponentEventRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxComponentEventRequestHandler.java index 028f3b5..c4a852e 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxComponentEventRequestHandler.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxComponentEventRequestHandler.java @@ -1,4 +1,4 @@ -// Copyright 2007, 2008, 2010, 2011, 2012 The Apache Software Foundation +// Copyright 2007-2013 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,7 +14,8 @@ package org.apache.tapestry5.internal.services; -import org.apache.tapestry5.ContentType; +import java.io.IOException; + import org.apache.tapestry5.TrackableComponentEventCallback; import org.apache.tapestry5.internal.InternalConstants; import org.apache.tapestry5.internal.structure.ComponentPageElement; @@ -22,15 +23,18 @@ import org.apache.tapestry5.internal.structure.Page; import org.apache.tapestry5.internal.util.Holder; import org.apache.tapestry5.ioc.internal.util.TapestryException; import org.apache.tapestry5.json.JSONObject; -import org.apache.tapestry5.services.*; - -import java.io.IOException; +import org.apache.tapestry5.services.Ajax; +import org.apache.tapestry5.services.ComponentEventRequestHandler; +import org.apache.tapestry5.services.ComponentEventRequestParameters; +import org.apache.tapestry5.services.ComponentEventResultProcessor; +import org.apache.tapestry5.services.Environment; +import org.apache.tapestry5.services.Request; /** * Similar to {@link ComponentEventRequestHandlerImpl}, but built around the Ajax request cycle, where the action * request sends back an immediate JSON response containing the new content. */ -@SuppressWarnings("unchecked") +@SuppressWarnings({"unchecked", "rawtypes"}) public class AjaxComponentEventRequestHandler implements ComponentEventRequestHandler { private final RequestPageCache cache; @@ -41,8 +45,6 @@ public class AjaxComponentEventRequestHandler implements ComponentEventRequestHa private final ComponentEventResultProcessor resultProcessor; - private final PageContentTypeAnalyzer pageContentTypeAnalyzer; - private final Environment environment; private final AjaxPartialResponseRenderer partialRenderer; @@ -51,14 +53,13 @@ public class AjaxComponentEventRequestHandler implements ComponentEventRequestHa public AjaxComponentEventRequestHandler(RequestPageCache cache, Request request, PageRenderQueue queue, @Ajax ComponentEventResultProcessor resultProcessor, PageActivator pageActivator, - PageContentTypeAnalyzer pageContentTypeAnalyzer, Environment environment, + Environment environment, AjaxPartialResponseRenderer partialRenderer) { this.cache = cache; this.queue = queue; this.resultProcessor = resultProcessor; this.pageActivator = pageActivator; - this.pageContentTypeAnalyzer = pageContentTypeAnalyzer; this.request = request; this.environment = environment; this.partialRenderer = partialRenderer; @@ -90,9 +91,7 @@ public class AjaxComponentEventRequestHandler implements ComponentEventRequestHa .getPageActivationContext(), interceptor)) return; - ContentType contentType = pageContentTypeAnalyzer.findContentType(activePage); - - request.setAttribute(InternalConstants.CONTENT_TYPE_ATTRIBUTE_NAME, contentType); + request.setAttribute(InternalConstants.PAGE_NAME_ATTRIBUTE_NAME, parameters.getActivePageName()); Page containerPage = cache.get(parameters.getContainingPageName()); http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1fe4fa73/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxPartialResponseRendererImpl.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxPartialResponseRendererImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxPartialResponseRendererImpl.java index d5f3d6a..aa69acf 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxPartialResponseRendererImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxPartialResponseRendererImpl.java @@ -78,11 +78,11 @@ public class AjaxPartialResponseRendererImpl implements AjaxPartialResponseRende // separated, and trying to keep stateless and stateful (i.e., perthread scope) services // separated. So we inform the stateful queue service what it needs to do here ... - ContentType pageContentType = (ContentType) request.getAttribute(InternalConstants.CONTENT_TYPE_ATTRIBUTE_NAME); - ContentType contentType = new ContentType(InternalConstants.JSON_MIME_TYPE, outputEncoding); + + String pageName = (String) request.getAttribute(InternalConstants.PAGE_NAME_ATTRIBUTE_NAME); - MarkupWriter writer = factory.newPartialMarkupWriter(pageContentType); + MarkupWriter writer = factory.newPartialMarkupWriter(pageName); // ... and here, the pipeline eventually reaches the PRQ to let it render the root render command. http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1fe4fa73/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MarkupWriterFactoryImpl.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MarkupWriterFactoryImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MarkupWriterFactoryImpl.java index 8dc6a51..48edfe0 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MarkupWriterFactoryImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MarkupWriterFactoryImpl.java @@ -1,4 +1,4 @@ -// Copyright 2007, 2008, 2009 The Apache Software Foundation +// Copyright 2007, 2008, 2009, 2013 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,19 +14,32 @@ package org.apache.tapestry5.internal.services; +import java.util.List; + import org.apache.tapestry5.ContentType; import org.apache.tapestry5.MarkupWriter; import org.apache.tapestry5.dom.DefaultMarkupModel; +import org.apache.tapestry5.dom.Html5MarkupModel; import org.apache.tapestry5.dom.MarkupModel; import org.apache.tapestry5.dom.XMLMarkupModel; +import org.apache.tapestry5.internal.parser.DTDToken; +import org.apache.tapestry5.internal.parser.TemplateToken; +import org.apache.tapestry5.internal.parser.TokenType; import org.apache.tapestry5.internal.structure.Page; +import org.apache.tapestry5.model.ComponentModel; import org.apache.tapestry5.services.MarkupWriterFactory; +import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer; +import org.apache.tapestry5.services.pageload.ComponentResourceSelector; public class MarkupWriterFactoryImpl implements MarkupWriterFactory { - private final PageContentTypeAnalyzer analyzer; + private final PageContentTypeAnalyzer pageContentTypeAnalyzer; private final RequestPageCache cache; + + private final ComponentTemplateSource templateSource; + + private final ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer; private final MarkupModel htmlModel = new DefaultMarkupModel(); @@ -35,32 +48,41 @@ public class MarkupWriterFactoryImpl implements MarkupWriterFactory private final MarkupModel htmlPartialModel = new DefaultMarkupModel(true); private final MarkupModel xmlPartialModel = new XMLMarkupModel(true); - - public MarkupWriterFactoryImpl(PageContentTypeAnalyzer analyzer, RequestPageCache cache) + + private final MarkupModel html5Model = new Html5MarkupModel(); + + private final MarkupModel html5PartialModel = new Html5MarkupModel(true); + + public MarkupWriterFactoryImpl(PageContentTypeAnalyzer pageContentTypeAnalyzer, + RequestPageCache cache, ComponentTemplateSource templateSource, + ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer) { - this.analyzer = analyzer; + this.pageContentTypeAnalyzer = pageContentTypeAnalyzer; this.cache = cache; + this.templateSource = templateSource; + this.componentRequestSelectorAnalyzer = componentRequestSelectorAnalyzer; } public MarkupWriter newMarkupWriter(ContentType contentType) { - return newMarkupWriter(contentType, false); + return constructMarkupWriter(contentType, false, false); } public MarkupWriter newPartialMarkupWriter(ContentType contentType) { - return newMarkupWriter(contentType, true); + return constructMarkupWriter(contentType, true, false); } - @SuppressWarnings({"UnusedDeclaration"}) - private MarkupWriter newMarkupWriter(ContentType contentType, boolean partial) + private MarkupWriter constructMarkupWriter(ContentType contentType, boolean partial, boolean HTML5) { boolean isHTML = contentType.getMimeType().equalsIgnoreCase("text/html"); - MarkupModel model = partial - ? (isHTML ? htmlPartialModel : xmlPartialModel) - : (isHTML ? htmlModel : xmlModel); - + MarkupModel model; + + if(isHTML) + model = HTML5 ? (partial ? html5PartialModel : html5Model) : (partial ? htmlPartialModel : htmlModel); + else + model = partial ? xmlPartialModel : xmlModel; // The charset parameter sets the encoding attribute of the XML declaration, if // not null and if using the XML model. @@ -71,8 +93,53 @@ public class MarkupWriterFactoryImpl implements MarkupWriterFactory { Page page = cache.get(pageName); - ContentType contentType = analyzer.findContentType(page); + return newMarkupWriter(page); + } + + private boolean hasHTML5Doctype(Page page) + { + ComponentModel componentModel = page.getRootComponent().getComponentResources().getComponentModel(); + + ComponentResourceSelector selector = componentRequestSelectorAnalyzer.buildSelectorForRequest(); + + List<TemplateToken> tokens = templateSource.getTemplate(componentModel, selector).getTokens(); + + DTDToken dtd = null; + + for(TemplateToken token : tokens) + { + if(token.getTokenType() == TokenType.DTD) + { + dtd = (DTDToken) token; + break; + } + } + + return dtd != null && dtd.name.equalsIgnoreCase("html") && dtd.publicId == null && dtd.systemId == null; + } + + public MarkupWriter newMarkupWriter(Page page) + { + boolean isHTML5 = hasHTML5Doctype(page); + + ContentType contentType = pageContentTypeAnalyzer.findContentType(page); + + return constructMarkupWriter(contentType, false, isHTML5); + } - return newMarkupWriter(contentType); + public MarkupWriter newPartialMarkupWriter(Page page) + { + boolean isHTML5 = hasHTML5Doctype(page); + + ContentType contentType = pageContentTypeAnalyzer.findContentType(page); + + return constructMarkupWriter(contentType, true, isHTML5); + } + + public MarkupWriter newPartialMarkupWriter(String pageName) + { + Page page = cache.get(pageName); + + return newPartialMarkupWriter(page); } } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1fe4fa73/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageResponseRendererImpl.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageResponseRendererImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageResponseRendererImpl.java index 093c467..4231e20 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageResponseRendererImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageResponseRendererImpl.java @@ -1,4 +1,4 @@ -// Copyright 2006, 2007, 2008, 2009, 2010 The Apache Software Foundation +// Copyright 2006-2013 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -58,11 +58,8 @@ public class PageResponseRendererImpl implements PageResponseRenderer requestGlobals.storeActivePageName(page.getName()); ContentType contentType = pageContentTypeAnalyzer.findContentType(page); - - // For the moment, the content type is all that's used determine the model for the markup writer. - // It's something of a can of worms. - - MarkupWriter writer = markupWriterFactory.newMarkupWriter(contentType); + + MarkupWriter writer = markupWriterFactory.newMarkupWriter(page); markupRenderer.renderPageMarkup(page, writer); http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1fe4fa73/tapestry-core/src/main/java/org/apache/tapestry5/services/MarkupWriterFactory.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/MarkupWriterFactory.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/MarkupWriterFactory.java index 2445456..b00c338 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/services/MarkupWriterFactory.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/MarkupWriterFactory.java @@ -1,4 +1,4 @@ -// Copyright 2006, 2007, 2008 The Apache Software Foundation +// Copyright 2006-2013 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.apache.tapestry5.services; import org.apache.tapestry5.ContentType; import org.apache.tapestry5.MarkupWriter; +import org.apache.tapestry5.internal.structure.Page; /** * Source for {@link org.apache.tapestry5.MarkupWriter} instances. @@ -28,6 +29,8 @@ public interface MarkupWriterFactory * @param contentType type of content generated by the markup write; used to control the type of {@link * org.apache.tapestry5.dom.MarkupModel} used with the {@link org.apache.tapestry5.dom.Document} * the backs the markup writer. + * + * @deprecated use {@link #newMarkupWriter(Page)} instead which doesn't rely on the content type alone. */ MarkupWriter newMarkupWriter(ContentType contentType); @@ -38,14 +41,55 @@ public interface MarkupWriterFactory * @param contentType type of content generated by the markup write; used to control the type of {@link * org.apache.tapestry5.dom.MarkupModel} used with the {@link org.apache.tapestry5.dom.Document} * the backs the markup writer. + * + * @deprecated use {@link #newPartialMarkupWriter(Page)} instead which doesn't rely on the content type alone. */ MarkupWriter newPartialMarkupWriter(ContentType contentType); /** * Obtains a markup writer that will render the content for the provided logical page name. - * - * @param pageName logical page name + * Convenience method for {@link #newMarkupWriter(Page)} + * + * @param pageName + * logical page name * @return writer configured for the page */ MarkupWriter newMarkupWriter(String pageName); + + /** + * Obtains a markup writer that will render the content for the provided logical page name, + * configured for <em>partial page rendering</em> (i.e., for a response to an Ajax request). + * Convenience method for {@link #newPartialMarkupWriter(Page)} + * + * @param pageName + * logical page name + * @return writer configured for the page + * + * @since 5.4 + */ + MarkupWriter newPartialMarkupWriter(String pageName); + + /** + * Obtains a markup writer that will render the content for the provided page. Takes into + * account all necessary information such as the page's content type and doctype. + * + * @param page the page to obtain a writer for + * @return writer configured for the page + * + * @since 5.4 + */ + MarkupWriter newMarkupWriter(Page page); + + /** + * Obtains a markup writer that will render the content for the provided page, + * configured for <em>partial page rendering</em> (i.e., for a response to an Ajax request). + * Takes into account all necessary information such as the page's content type and doctype. + * + * @param page + * the page to obtain a writer for + * @return writer configured for the page + * + * @since 5.4 + */ + MarkupWriter newPartialMarkupWriter(Page page); } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1fe4fa73/tapestry-core/src/test/java/org/apache/tapestry5/dom/DOMTest.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/dom/DOMTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/dom/DOMTest.java index 5a19530..4f152e6 100644 --- a/tapestry-core/src/test/java/org/apache/tapestry5/dom/DOMTest.java +++ b/tapestry-core/src/test/java/org/apache/tapestry5/dom/DOMTest.java @@ -14,17 +14,17 @@ package org.apache.tapestry5.dom; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; + import org.apache.tapestry5.MarkupWriter; import org.apache.tapestry5.internal.services.MarkupWriterImpl; import org.apache.tapestry5.internal.test.InternalBaseTestCase; import org.apache.tapestry5.ioc.internal.util.CollectionFactory; import org.testng.annotations.Test; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Set; - /** * Tests for a number of DOM node classes, including {@link org.apache.tapestry5.dom.Element} and {@link * org.apache.tapestry5.dom.Document}. @@ -951,4 +951,30 @@ public class DOMTest extends InternalBaseTestCase assertEquals(writer.toString(), "<?xml version=\"1.0\"?>\n" + "<ul><li>0</li><li>1</li><li>3</li></ul>"); } + + /** + * TAP5-2071 + */ + @Test + public void html5_void_elements() + { + final List<String> voidElements = CollectionFactory.newList("area", "base", "br", "col", + "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", + "source", "track", "wbr"); + + MarkupWriter writer = new MarkupWriterImpl(new Html5MarkupModel()); + + writer.element("html"); + + for(String element : voidElements) + { + writer.element(element); + writer.end(); + } + + writer.end(); + + assertEquals(writer.toString(), + "<html><area><base><br><col><command><embed><hr><img><input><keygen><link><meta><param><source><track><wbr></html>"); + } }
