Skip to content

Commit

Permalink
Build release candidate.
Browse files Browse the repository at this point in the history
  • Loading branch information
baron1405 committed Apr 14, 2024
1 parent 6c561b5 commit ed3ec77
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 120 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- XML characters outside the [Unicode Basic Multilingual Plane](https://en.wikipedia.org/wiki/Plane_(Unicode))
(i.e. 0x10000-0x10FFFF) are now supported and escaped
- The `setEscapeNonAscii` and `setUseDecimal` methods have been added to control escaping behavior
- [Dependency analysis Gradle plugin](https://github.com/autonomousapps/dependency-analysis-gradle-plugin)
- The `check` task now depends on the `buildHealth` task and will fail the build on health violations such as
unused dependencies
- New dependency on the [escapers](https://central.sonatype.com/artifact/org.cthing/escapers) library

### Changed

- Numeric character entities are now written in hexidecimal (e.g. `©`) rather than decimal
- The escape behavior has changed. By default, characters outside the ASCII range are no longer escaped. To
escape these characters, call `setEscapeNonAscii(true)`.
- By default, numeric character entities are now written in hexidecimal (e.g. `©`) rather than decimal.
To write numeric entities in decimal, call `setUseDecimal(true)`.
- Invalid XML characters are no longer written. In previous versions, they were written in decimal with the prefix
"ctrl-".
- Changed JSR-305 dependency from `implementation` to `api`

### Removed

- The `setEscaping` method has been removed. Use the `setEscapeNonAscii` and `setUseDecimal` methods.

## [2.0.1] - 2023-12-23

### Added
Expand Down
5 changes: 3 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ plugins {
alias(libs.plugins.versions)
}

val baseVersion = "2.1.0"
val isSnapshot = true
val baseVersion = "3.0.0"
val isSnapshot = false

val isCIServer = System.getenv("CTHING_CI") != null
val buildNumber = if (isCIServer) System.currentTimeMillis().toString() else "0"
Expand All @@ -37,6 +37,7 @@ dependencies {
api(libs.jsr305)

implementation(libs.cthingAnnots)
implementation(libs.escapers)

testImplementation(libs.junitApi)
testImplementation(libs.junitParams)
Expand Down
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ versions = { id = "com.github.ben-manes.versions", version = "0.51.0" }
apiGuardian = "org.apiguardian:apiguardian-api:1.1.2"
assertJ = "org.assertj:assertj-core:3.25.3"
cthingAnnots = "org.cthing:cthing-annotations:1.0.0"
escapers = "org.cthing:escapers:1.0.0"
jsr305 = "com.google.code.findbugs:jsr305:3.0.2"
junitApi = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
junitEngine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
Expand Down
116 changes: 46 additions & 70 deletions src/main/java/org/cthing/xmlwriter/XmlWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand All @@ -35,6 +36,7 @@
import javax.xml.XMLConstants;

import org.cthing.annotations.AccessForTesting;
import org.cthing.escapers.XmlEscaper;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
Expand Down Expand Up @@ -361,8 +363,8 @@ private enum Event {
/** Should output be formatted. */
private boolean prettyPrint;

/** Should characters be escaped. */
private boolean escaping;
/** Options controlling the escaping behavior. */
private final Set<XmlEscaper.Option> escapeOptions;

/** Indent string. */
private String indentStr;
Expand Down Expand Up @@ -563,7 +565,7 @@ public XmlWriter(@Nullable @WillNotClose final XMLReader reader, @Nullable @Will
this.nsDeclMap = new HashMap<>();
this.nsRootDeclSet = new HashSet<>();
this.prettyPrint = false;
this.escaping = true;
this.escapeOptions = EnumSet.noneOf(XmlEscaper.Option.class);
this.minimize = true;
this.indentStr = DEF_INDENT;
this.offsetStr = DEF_OFFSET;
Expand Down Expand Up @@ -720,26 +722,50 @@ public boolean getPrettyPrint() {
}

/**
* Enables or disables XML escaping of attribute values and character data. In addition, enables or disables
* escaping of embedded quotes in attribute values. By default, escaping is enabled.
* Escape characters above the ASCII range (i.e. ch &gt; 0x7F). By default, only ASCII control characters
* and markup-significant ASCII characters are escaped. Specifying this option causes all ISO Latin-1,
* Unicode BMP and surrogate pair characters to be escaped.
*
* <p><strong>WARNING:</strong> Under normal operating conditions, escaping should always be enabled. If the
* character data to be written is known to not require escaping, disabling escaping will improve performance.
* Use this feature at your own risk.
* @param enable {@code true} to escape characters outside the ASCII range using numerical entity references
*/
public void setEscapeNonAscii(final boolean enable) {
if (enable) {
this.escapeOptions.add(XmlEscaper.Option.ESCAPE_NON_ASCII);
} else {
this.escapeOptions.remove(XmlEscaper.Option.ESCAPE_NON_ASCII);
}
}

/**
* Indicates whether characters above the ASCII range (i.e. ch &gt; 0x7F) are escaped.
*
* @param enable {@code true} to enable escaping (the default).
* @return {@code true} if characters outside the ASCII range are being escaped.
*/
public void setEscaping(final boolean enable) {
this.escaping = enable;
public boolean getEscapeNonAscii() {
return this.escapeOptions.contains(XmlEscaper.Option.ESCAPE_NON_ASCII);
}

/**
* Indicates whether XML escaping is enabled.
* Use decimal for numerical character entities (i.e. &amp;#DDDD;). By default, this library uses hexadecimal
* (i.e. &amp;#xHHH;) for numerical character entities.
*
* @return Whether XML escaping is enabled or disabled.
* @param enable {@code true} to use decimal rather than hexadecimal for numerical character entities
*/
public boolean getEscaping() {
return this.escaping;
public void setUseDecimal(final boolean enable) {
if (enable) {
this.escapeOptions.add(XmlEscaper.Option.USE_DECIMAL);
} else {
this.escapeOptions.remove(XmlEscaper.Option.USE_DECIMAL);
}
}

/**
* Indicates whether decimal is being used for numerical character entities rather than hexadecimal.
*
* @return {@code true} if decimal is being used for numerical character entities.
*/
public boolean getUseDecimal() {
return this.escapeOptions.contains(XmlEscaper.Option.USE_DECIMAL);
}

/**
Expand Down Expand Up @@ -2586,21 +2612,7 @@ void writeQuoted(final String s) throws SAXException {
@AccessForTesting
void writeQuoted(final char[] carr, final int start, final int length) throws SAXException {
writeRaw('"');
if (this.escaping && containsQuotes(carr, start, length)) {
final int end = start + length;
for (int i = start; i < end; i++) {
final char c = carr[i];
if (c == '"') {
writeRaw("&quot;");
} else if (c == '\'') {
writeRaw("&apos;");
} else {
writeEscaped(c);
}
}
} else {
writeEscaped(carr, start, length);
}
writeEscaped(carr, start, length);
writeRaw('"');
}

Expand All @@ -2616,46 +2628,10 @@ void writeQuoted(final char[] carr, final int start, final int length) throws SA
*/
@AccessForTesting
void writeEscaped(final char[] carr, final int start, final int length) throws SAXException {
if (this.escaping) {
final int end = start + length;
int i = start;
while (i < end) {
final int codePoint = Character.codePointAt(carr, i);
writeEscaped(codePoint);
i += Character.charCount(codePoint);
}
} else {
writeRaw(carr, start, length);
}
}

/**
* Writes the specified character to the output escaping the '&amp;', '&lt;', and '&gt;' characters using the
* standard XML escape sequences. Control characters and characters outside the ASCII range are escaped using a
* numeric character reference. Invalid XML characters are not written.
*
* @param c Character to write
* @throws SAXException If there is an error writing the character. The SAXException wraps an IOException.
*/
@AccessForTesting
void writeEscaped(final int c) throws SAXException {
switch (c) {
case '&' -> writeRaw("&amp;");
case '<' -> writeRaw("&lt;");
case '>' -> writeRaw("&gt;");
case '\n' -> writeNewline();
case '\t', '\r' -> writeRaw((char)c);
default -> {
if (c > 0x001F && c < 0x007F) {
writeRaw((char)c);
} else if ((c >= 0x007F && c <= 0xD7FF)
|| (c >= 0xE000 && c <= 0xFFFD)
|| (c >= 0x10000 && c <= 0x10FFFF)) {
writeRaw("&#x");
writeRaw(Integer.toHexString(c).toUpperCase());
writeRaw(';');
}
}
try {
XmlEscaper.escape(carr, start, length, this.out, this.escapeOptions);
} catch (final IOException ex) {
throw new SAXException(ex);
}
}

Expand Down
66 changes: 19 additions & 47 deletions src/test/java/org/cthing/xmlwriter/XmlWriterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import org.xml.sax.helpers.AttributesImpl;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;


@SuppressWarnings({ "HttpUrlsUsage", "UnnecessaryUnicodeEscape" })
Expand Down Expand Up @@ -91,60 +90,43 @@ void testWriteRawChar() throws Exception {
@DisplayName("Write an array with escaping")
void testWriteEscapedArray() throws Exception {
final String testStringIn = "<Hello &<>\" World\u00A9\u001A\uFFFE\uD83D\uDE03\t\n";
final String testStringOut = "&lt;Hello &amp;&lt;&gt;\" World&#xA9;&#x1F603;\t\n";
final String testStringOut = "&lt;Hello &amp;&lt;&gt;&quot; World\u00A9\uD83D\uDE03\t\n";

this.xmlWriter.writeEscaped(testStringIn.toCharArray(), 0, testStringIn.length());

assertThat(this.stringWriter).hasToString(testStringOut);
}

public static Stream<Arguments> writeEscapedProvider() {
return Stream.of(
arguments(' ', " "),
arguments('a', "a"),
arguments('Z', "Z"),
arguments('~', "~"),
arguments('&', "&amp;"),
arguments('<', "&lt;"),
arguments('>', "&gt;"),
arguments('\n', "\n"),
arguments('\t', "\t"),
arguments('\r', "\r"),
arguments(0x0, ""),
arguments(0x1F, ""),
arguments(0xFFFF, ""),
arguments(0x7F, "&#x7F;"),
arguments(0xD7FF, "&#xD7FF;"),
arguments(0xE000, "&#xE000;"),
arguments(0xFFFD, "&#xFFFD;"),
arguments(0x1F603, "&#x1F603;")
);
}
@Test
@DisplayName("Write an array with escaping")
void testWriteEscapedArrayNonAscii() throws Exception {
final String testStringIn = "<Hello &<>\" World\u00A9\u001A\uFFFE\uD83D\uDE03\t\n";
final String testStringOut = "&lt;Hello &amp;&lt;&gt;&quot; World&#xA9;&#x1F603;\t\n";

@ParameterizedTest
@MethodSource("writeEscapedProvider")
@DisplayName("Write a character with escaping")
void testWriteEscapedChar(final int ch, final String expected) throws Exception {
this.xmlWriter.writeEscaped(ch);
assertThat(this.stringWriter).hasToString(expected);
this.xmlWriter.setEscapeNonAscii(true);
this.xmlWriter.writeEscaped(testStringIn.toCharArray(), 0, testStringIn.length());

assertThat(this.stringWriter).hasToString(testStringOut);
}

@Test
@DisplayName("Write a string with escaping disabled")
void testWriteEscapedDisabled() throws Exception {
final String testStringIn = "<Hello &<>\" World\u00A9\u001A\t\n";
this.xmlWriter.setEscaping(false);
@DisplayName("Write an array with escaping")
void testWriteEscapedArrayNonAsciiDecimal() throws Exception {
final String testStringIn = "<Hello &<>\" World\u00A9\u001A\uFFFE\uD83D\uDE03\t\n";
final String testStringOut = "&lt;Hello &amp;&lt;&gt;&quot; World&#169;&#128515;\t\n";

this.xmlWriter.setEscapeNonAscii(true);
this.xmlWriter.setUseDecimal(true);
this.xmlWriter.writeEscaped(testStringIn.toCharArray(), 0, testStringIn.length());

assertThat(this.stringWriter).hasToString(testStringIn);
assertThat(this.stringWriter).hasToString(testStringOut);
}

@Test
@DisplayName("Write a string adding quotes")
void testWriteQuotedString() throws Exception {
final String testStringIn = "Hello &<>\"' World\u00A9";
final String testStringOut = "\"Hello &amp;&lt;&gt;&quot;&apos; World&#xA9;\"";
final String testStringOut = "\"Hello &amp;&lt;&gt;&quot;&apos; World\u00A9\"";

this.xmlWriter.writeQuoted(testStringIn);

Expand All @@ -157,22 +139,12 @@ void testWriteQuotedArray() throws Exception {
final String testStringIn = "Hello &<>\"' World\u00A9";
final String testStringOut = "\"Hello &amp;&lt;&gt;&quot;&apos; World&#xA9;\"";

this.xmlWriter.setEscapeNonAscii(true);
this.xmlWriter.writeQuoted(testStringIn.toCharArray(), 0, testStringIn.length());

assertThat(this.stringWriter).hasToString(testStringOut);
}

@Test
@DisplayName("Write string adding quotes with escaping disabled")
void testWriteQuotedNoEscaping() throws Exception {
final String testStringIn = "Hello &<>\"' World\u00A9";
this.xmlWriter.setEscaping(false);

this.xmlWriter.writeQuoted(testStringIn);

assertThat(this.stringWriter).hasToString("\"" + testStringIn + "\"");
}

@ParameterizedTest
@MethodSource("minimalDocumentProvider")
@DisplayName("Write a minimal XML document")
Expand Down

0 comments on commit ed3ec77

Please sign in to comment.