Skip to content

Commit

Permalink
Fix output produced by XmlUtil must not contain platform-dependent li…
Browse files Browse the repository at this point in the history
…ne separators.
  • Loading branch information
wrandelshofer committed Dec 22, 2023
1 parent 352f324 commit 73ebdc9
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,8 @@ public Document toDocument(@NonNull Node drawingNode, @Nullable CssDimension2D s
builder.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader("")));
Document doc = builder.newDocument();
DOMResult result = new DOMResult(doc);
XMLStreamWriter w = XMLOutputFactory.newInstance().createXMLStreamWriter(result);
XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
XMLStreamWriter w = xmlOutputFactory.createXMLStreamWriter(result);
writeDocument(w, drawingNode, size);
w.close();
return doc;
Expand Down Expand Up @@ -569,7 +570,7 @@ private void writeClassAttribute(@NonNull XMLStreamWriter w, @NonNull Node node)
if (!styleClass.isEmpty()) {
StringBuilder buf = new StringBuilder();
for (String clazz : styleClass) {
if (buf.length() != 0) {
if (!buf.isEmpty()) {
buf.append(' ');
}
buf.append(clazz);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,41 +28,47 @@ public class FXSvgTinyWriterTest {
public @NonNull List<DynamicTest> dynamicTestsExportToWriter() {
return Arrays.asList(
dynamicTest("rect", () -> testExportToWriter(new Rectangle(10, 20, 100, 200),
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
"<svg baseProfile=\"tiny\" version=\"1.2\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
" <rect fill=\"#000000\" height=\"200\" width=\"100\" x=\"10\" y=\"20\"/>\n" +
"</svg>")),
"""
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg baseProfile="tiny" version="1.2" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<rect fill="#000000" height="200" width="100" x="10" y="20"/>
</svg>""")),
dynamicTest("text", () -> testExportToWriter(new Text(10, 20, "Hello"),
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
"<svg baseProfile=\"tiny\" version=\"1.2\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
" <text fill=\"#000000\" font-family=\"'System Regular', 'System'\" font-size=\"13\" x=\"10\" y=\"20\">Hello</text>\n" +
"</svg>")),
"""
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg baseProfile="tiny" version="1.2" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<text fill="#000000" font-family="'System Regular', 'System'" font-size="13" x="10" y="20">Hello</text>
</svg>""")),
dynamicTest("text escape", () -> testExportToWriter(new Text(10, 20, "&<>\""),
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
"<svg baseProfile=\"tiny\" version=\"1.2\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
" <text fill=\"#000000\" font-family=\"'System Regular', 'System'\" font-size=\"13\" x=\"10\" y=\"20\">&amp;&lt;&gt;\"</text>\n" +
"</svg>"))
"""
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg baseProfile="tiny" version="1.2" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<text fill="#000000" font-family="'System Regular', 'System'" font-size="13" x="10" y="20">&amp;&lt;&gt;"</text>
</svg>"""))
);
}

@TestFactory
public @NonNull List<DynamicTest> dynamicTestsExportToDOM() {
return Arrays.asList(
dynamicTest("rect", () -> testExportToDOM(new Rectangle(10, 20, 100, 200),
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" baseProfile=\"tiny\" version=\"1.2\">\n" +
" <rect fill=\"#000000\" height=\"200\" width=\"100\" x=\"10\" y=\"20\"/>\n" +
"</svg>\n")),
"""
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg baseProfile="tiny" version="1.2" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<rect fill="#000000" height="200" width="100" x="10" y="20"/>
</svg>""")),
dynamicTest("text", () -> testExportToDOM(new Text(10, 20, "Hello"),
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" baseProfile=\"tiny\" version=\"1.2\">\n" +
" <text fill=\"#000000\" font-family=\"'System Regular', 'System'\" font-size=\"13\" x=\"10\" y=\"20\">Hello</text>\n" +
"</svg>\n")),
"""
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg baseProfile="tiny" version="1.2" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<text fill="#000000" font-family="'System Regular', 'System'" font-size="13" x="10" y="20">Hello</text>
</svg>""")),
dynamicTest("text escape", () -> testExportToWriter(new Text(10, 20, "&<>\""),
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
"<svg baseProfile=\"tiny\" version=\"1.2\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
" <text fill=\"#000000\" font-family=\"'System Regular', 'System'\" font-size=\"13\" x=\"10\" y=\"20\">&amp;&lt;&gt;\"</text>\n" +
"</svg>"))
"""
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg baseProfile="tiny" version="1.2" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<text fill="#000000" font-family="'System Regular', 'System'" font-size="13" x="10" y="20">&amp;&lt;&gt;"</text>
</svg>"""))
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

/**
Expand Down Expand Up @@ -273,13 +276,13 @@ public class IndentingXMLStreamWriter implements XMLStreamWriter, AutoCloseable
private static final String XML_SPACE_PRESERVE_VALUE = "preserve";
private static final String XMLNS_NAMESPACE = "https://www.w3.org/TR/REC-xml-names/";
private String indentation = " ";
private static final String LINE_BREAK = "\n";
private String lineSeparator = "\n";
private final Writer w;
/**
* Invariant: this stack always contains at least the root element.
*/
private final Deque<Element> stack = new ArrayDeque<>();
private final TreeSet<Attribute> attributes = new TreeSet<>(Comparator.comparing(Attribute::getNamespace).thenComparing(Attribute::getLocalName));
private Set<Attribute> attributes = new TreeSet<>(Comparator.comparing(Attribute::getNamespace).thenComparing(Attribute::getLocalName));
private final CharsetEncoder encoder;
private boolean isStartTagOpen = false;
private boolean escapeClosingAngleBracket = true;
Expand Down Expand Up @@ -311,6 +314,15 @@ public String getIndentation() {
return indentation;
}

public void setSortAttributes(boolean b) {
attributes = b ? new TreeSet<>(Comparator.comparing(Attribute::getNamespace).thenComparing(Attribute::getLocalName))
: new LinkedHashSet<>();
}

public boolean isSortAttributes() {
return attributes instanceof SortedSet<Attribute>;
}

/**
* Whether to replace {@literal '<'} and {@literal '>} characters by
* entity references.
Expand All @@ -332,6 +344,14 @@ public void setIndentation(String indentation) {
this.indentation = indentation;
}

public String getLineSeparator() {
return lineSeparator;
}

public void setLineSeparator(String lineSeparator) {
this.lineSeparator = lineSeparator;
}

private void closeStartTagOrCloseEmptyElemTag() throws XMLStreamException {
charBuffer.setLength(0);
if (isStartTagOpen) {
Expand Down Expand Up @@ -548,7 +568,7 @@ public void writeCharacters(@NonNull String text) throws XMLStreamException {
return;
} else {
setHasContent(true);
if (charBuffer.length() > 0) {
if (!charBuffer.isEmpty()) {
writeXmlContent(charBuffer.toString(), false, false);
charBuffer.setLength(0);
}
Expand All @@ -567,7 +587,7 @@ public void writeCharacters(char @NonNull [] text, int start, int len) throws XM
return;
} else {
setHasContent(true);
if (charBuffer.length() > 0) {
if (!charBuffer.isEmpty()) {
writeXmlContent(charBuffer.toString(), false, false);
charBuffer.setLength(0);
}
Expand Down Expand Up @@ -685,7 +705,7 @@ public void writeEndElement() throws XMLStreamException {
}

private void writeEndElementLineBreakAndIndentation() throws XMLStreamException {
write(LINE_BREAK);
write(lineSeparator);
for (int i = stack.size() - 2; i >= 0; i--) {
write(indentation);
}
Expand All @@ -701,7 +721,7 @@ public void writeEntityRef(String name) throws XMLStreamException {
}

private void writeLineBreakAndIndentation() throws XMLStreamException {
write(LINE_BREAK);
write(lineSeparator);
for (int i = stack.size() - 3; i >= 0; i--) {
write(indentation);
}
Expand All @@ -712,7 +732,7 @@ public void writeNamespace(@NonNull String prefix, @NonNull String namespaceURI)
Objects.requireNonNull(prefix, "prefix");
Objects.requireNonNull(namespaceURI, "namespaceURI");
requireStartTagOpened();
attributes.add(new Attribute(prefix.isEmpty() ? "" : XMLNS_PREFIX,
attributes.add(new Attribute(prefix.isEmpty() || XMLNS_PREFIX.equals(prefix) ? "" : XMLNS_PREFIX,
XMLNS_NAMESPACE, prefix.isEmpty() ? XMLNS_PREFIX : prefix, namespaceURI));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stax.StAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
Expand All @@ -52,6 +53,7 @@
import java.io.Writer;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand All @@ -72,10 +74,17 @@ public class XmlUtil {
private static final String SEPARATOR = "\0";
private static final Properties DEFAULT_PROPERTIES = new Properties();

public static final String HTTP_XML_APACHE_ORG_XALAN_LINE_SEPARATOR = "{http://xml.apache.org/xalan}line-separator";

public static final String HTTP_XML_APACHE_ORG_XSLT_INDENT_AMOUNT = "{http://xml.apache.org/xslt}indent-amount";

public static final String CANONICAL_LINE_SEPARATOR = "\n";

static {
DEFAULT_PROPERTIES.put(OutputKeys.INDENT, "yes");
DEFAULT_PROPERTIES.put(OutputKeys.ENCODING, "UTF-8");
DEFAULT_PROPERTIES.put("{http://xml.apache.org/xslt}indent-amount", "2");
DEFAULT_PROPERTIES.put(HTTP_XML_APACHE_ORG_XSLT_INDENT_AMOUNT, "2");
DEFAULT_PROPERTIES.put(HTTP_XML_APACHE_ORG_XALAN_LINE_SEPARATOR, CANONICAL_LINE_SEPARATOR);
}

private XmlUtil() {
Expand Down Expand Up @@ -246,12 +255,16 @@ public static void write(@NonNull Path out, Document doc, Properties outputPrope
write(result, doc, outputProperties);
}

public static void write(Result result, Document doc) throws IOException {
public static void write(@NonNull Result result, @NonNull Document doc) throws IOException {
write(result, doc, DEFAULT_PROPERTIES);
}

public static void write(Result result, Document doc, @Nullable Properties outputProperties) throws IOException {
public static void write(@NonNull Result result, @NonNull Document doc, @Nullable Properties outputProperties) throws IOException {
try {
// We replace the StreamResult by a StAXResult,
// because with a StreamResult we would produce platform-dependent line-breaks.
result = replaceStreamResultByStAXResult(result, outputProperties);

final TransformerFactory factory = TransformerFactory.newInstance();
Transformer t = factory.newTransformer();
if (outputProperties != null) {
Expand All @@ -264,6 +277,27 @@ public static void write(Result result, Document doc, @Nullable Properties outpu
}
}

private static Result replaceStreamResultByStAXResult(@NonNull Result result, @Nullable Properties outputProperties) {
if (result instanceof StreamResult sr) {
IndentingXMLStreamWriter w;
if (sr.getOutputStream() != null)
w = new IndentingXMLStreamWriter(sr.getOutputStream(), Charset.forName((String) outputProperties.getOrDefault(OutputKeys.ENCODING, "UTF-8")));
else {
w = new IndentingXMLStreamWriter(sr.getWriter());
}
//w.setSortAttributes(false);
try {
int indentation = Integer.parseInt((String) outputProperties.getOrDefault(HTTP_XML_APACHE_ORG_XSLT_INDENT_AMOUNT, "0"));
w.setIndentation(" ".repeat(indentation));
} catch (NumberFormatException e) {
// bail
}
w.setLineSeparator((String) outputProperties.getOrDefault(HTTP_XML_APACHE_ORG_XALAN_LINE_SEPARATOR, CANONICAL_LINE_SEPARATOR));
result = new StAXResult(w);
}
return result;
}

/**
* Returns a stream which iterates over the subtree starting at the
* specified node in preorder sequence.
Expand Down Expand Up @@ -361,38 +395,38 @@ public static String readNamespaceUri(Source source) throws IOException {
for (XMLStreamReader r = dbf.createXMLStreamReader(source); r.hasNext(); ) {
int next = r.next();
switch (next) {
case XMLStreamReader.START_ELEMENT:
return r.getNamespaceURI();
case XMLStreamReader.END_ELEMENT:
return null;
case XMLStreamReader.PROCESSING_INSTRUCTION:
break;
case XMLStreamReader.CHARACTERS:
break;
case XMLStreamReader.COMMENT:
break;
case XMLStreamReader.SPACE:
break;
case XMLStreamReader.START_DOCUMENT:
break;
case XMLStreamReader.END_DOCUMENT:
break;
case XMLStreamReader.ENTITY_REFERENCE:
break;
case XMLStreamReader.ATTRIBUTE:
break;
case XMLStreamReader.DTD:
break;
case XMLStreamReader.CDATA:
break;
case XMLStreamReader.NAMESPACE:
break;
case XMLStreamReader.NOTATION_DECLARATION:
break;
case XMLStreamReader.ENTITY_DECLARATION:
break;
default:
throw new IOException("unsupported XMLStream event: " + next);
case XMLStreamReader.START_ELEMENT:
return r.getNamespaceURI();
case XMLStreamReader.END_ELEMENT:
return null;
case XMLStreamReader.PROCESSING_INSTRUCTION:
break;
case XMLStreamReader.CHARACTERS:
break;
case XMLStreamReader.COMMENT:
break;
case XMLStreamReader.SPACE:
break;
case XMLStreamReader.START_DOCUMENT:
break;
case XMLStreamReader.END_DOCUMENT:
break;
case XMLStreamReader.ENTITY_REFERENCE:
break;
case XMLStreamReader.ATTRIBUTE:
break;
case XMLStreamReader.DTD:
break;
case XMLStreamReader.CDATA:
break;
case XMLStreamReader.NAMESPACE:
break;
case XMLStreamReader.NOTATION_DECLARATION:
break;
case XMLStreamReader.ENTITY_DECLARATION:
break;
default:
throw new IOException("unsupported XMLStream event: " + next);
}
}
} catch (XMLStreamException e) {
Expand Down

0 comments on commit 73ebdc9

Please sign in to comment.