diff --git a/nx01-x4o-sax3/src/main/java/org/x4o/sax3/SAX3WriterHtml.java b/nx01-x4o-sax3/src/main/java/org/x4o/sax3/SAX3WriterHtml.java index cbd3893..dffe14d 100644 --- a/nx01-x4o-sax3/src/main/java/org/x4o/sax3/SAX3WriterHtml.java +++ b/nx01-x4o-sax3/src/main/java/org/x4o/sax3/SAX3WriterHtml.java @@ -24,7 +24,10 @@ package org.x4o.sax3; import java.io.IOException; import java.io.Writer; +import java.util.Arrays; import java.util.Calendar; +import java.util.Collections; +import java.util.List; import org.x4o.sax3.io.ContentCloseable; import org.x4o.sax3.io.SAX3PropertyConfig; @@ -40,8 +43,11 @@ import org.xml.sax.helpers.AttributesImpl; */ public class SAX3WriterHtml extends SAX3WriterEnum { + static public final List HTML_VOID_TAGS = Collections.unmodifiableList(Tag.valuesVoidElement()); + public SAX3WriterHtml(Writer out, String encoding) { super(new SAX3WriterXml(out, encoding), "", SAX3XMLConstants.NULL_NS_URI); + getPropertyConfig().setProperty(SAX3WriterXml.OUTPUT_FOLD_EMPTY_TAGS, HTML_VOID_TAGS); } public SAX3PropertyConfig getPropertyConfig() { @@ -247,7 +253,7 @@ public class SAX3WriterHtml extends SAX3WriterEnum valuesVoidElement() { + return Arrays.stream(values()).filter(v -> v.voidElement()).map(v -> v.name()).toList(); + } } private final static String DOCTYPE_NAME = "HTML PUBLIC"; diff --git a/nx01-x4o-sax3/src/main/java/org/x4o/sax3/io/AbstractContentWriterHandler.java b/nx01-x4o-sax3/src/main/java/org/x4o/sax3/io/AbstractContentWriterHandler.java index dfe40c7..21093db 100644 --- a/nx01-x4o-sax3/src/main/java/org/x4o/sax3/io/AbstractContentWriterHandler.java +++ b/nx01-x4o-sax3/src/main/java/org/x4o/sax3/io/AbstractContentWriterHandler.java @@ -34,6 +34,7 @@ import java.io.Writer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -65,6 +66,7 @@ public class AbstractContentWriterHandler implements ContentHandler, Closeable { private boolean printReturn = false; private String lastElement = null; private Stack elements = null; + private Set foldEmptyTags = null; //@formatter:off private final static String PROPERTY_CONTEXT_PREFIX = SAX3PropertyConfig.X4O_PROPERTIES_PREFIX + "content/"; // TODO: change to "writer/xml" @@ -77,6 +79,7 @@ public class AbstractContentWriterHandler implements ContentHandler, Closeable { public final static String OUTPUT_COMMENT_AUTO_SPACE = PROPERTY_CONTEXT_PREFIX + "output/comment-auto-space"; public final static String OUTPUT_LINE_BREAK_WIDTH = PROPERTY_CONTEXT_PREFIX + "output/line-break-width"; public final static String OUTPUT_LINE_PER_ATTRIBUTE = PROPERTY_CONTEXT_PREFIX + "output/line-per-attribute"; + public final static String OUTPUT_FOLD_EMPTY_TAGS = PROPERTY_CONTEXT_PREFIX + "output/fold-empty-tags"; public final static String PROLOG_LICENCE_FILE = PROPERTY_CONTEXT_PREFIX + "prolog/licence-file"; public final static String PROLOG_LICENCE_RESOURCE = PROPERTY_CONTEXT_PREFIX + "prolog/licence-resource"; public final static String PROLOG_LICENCE_ENCODING = PROPERTY_CONTEXT_PREFIX + "prolog/licence-encoding"; @@ -96,6 +99,7 @@ public class AbstractContentWriterHandler implements ContentHandler, Closeable { new PropertyConfigItem(OUTPUT_COMMENT_AUTO_SPACE, Boolean.class, true), new PropertyConfigItem(OUTPUT_LINE_BREAK_WIDTH, Integer.class, -1), new PropertyConfigItem(OUTPUT_LINE_PER_ATTRIBUTE, Boolean.class, false), + new PropertyConfigItem(OUTPUT_FOLD_EMPTY_TAGS, List.class ), // if null|empty than all empty tags are folded new PropertyConfigItem(PROLOG_LICENCE_ENCODING, String.class, SAX3XMLConstants.XML_DEFAULT_ENCODING), new PropertyConfigItem(PROLOG_LICENCE_FILE, File.class ), new PropertyConfigItem(PROLOG_LICENCE_RESOURCE, String.class ), @@ -429,27 +433,29 @@ public class AbstractContentWriterHandler implements ContentHandler, Closeable { throw new SAXException("Unexpected end tag: " + localName + " should be: " + elements.peek()); } elements.pop(); - - if (startElement != null) { - String tag = startElement.toString(); - write(tag.substring(0, tag.length() - 1));// rm normal close - write(SAX3XMLConstants.TAG_CLOSE_EMPTY); - startElement = null; - indent--; - return; - } - indent--; - if (printReturn || !localName.equals(lastElement)) { - write(getPropertyConfig().getPropertyString(OUTPUT_CHAR_NEWLINE)); - writeIndent(); + if (startElement != null) { + // no child element of other content, thus use the empty body closing tag + if (allowEndElementFolding(localName)) { + String tag = startElement.toString(); + write(tag.substring(0, tag.length() - 1));// rm normal close + write(SAX3XMLConstants.TAG_CLOSE_EMPTY); + startElement = null; + return; + } + // void element not allowed, so print start + empty body on same line + autoCloseStartElement(); } else { - printReturn = true; + if (printReturn || !localName.equals(lastElement)) { + write(getPropertyConfig().getPropertyString(OUTPUT_CHAR_NEWLINE)); + writeIndent(); + } else { + printReturn = true; + } } if (localName == null) { localName = "null"; } - write(SAX3XMLConstants.TAG_OPEN_END); if (SAX3XMLConstants.NULL_NS_URI.equals(uri) || uri == null) { write(localName); @@ -470,6 +476,19 @@ public class AbstractContentWriterHandler implements ContentHandler, Closeable { } } + private boolean allowEndElementFolding(String tag) { + if (foldEmptyTags == null) { + foldEmptyTags = new HashSet<>(); + if (getPropertyConfig().getProperty(OUTPUT_FOLD_EMPTY_TAGS) != null) { + foldEmptyTags.addAll(getPropertyConfig().getPropertyList(OUTPUT_FOLD_EMPTY_TAGS)); + } + } + if (foldEmptyTags.isEmpty()) { + return true; + } + return foldEmptyTags.contains(tag); + } + /** * Starts the prefix mapping of an xml namespace uri. *