You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2024/01/21 20:49:49 UTC
(jena) branch main updated: GH-2167: Quoted URI output
This is an automated email from the ASF dual-hosted git repository.
andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git
The following commit(s) were added to refs/heads/main by this push:
new 20dfb6952b GH-2167: Quoted URI output
20dfb6952b is described below
commit 20dfb6952be99d4f30c0f79bdf8f4f3a538f45dd
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Sun Jan 14 10:20:28 2024 +0000
GH-2167: Quoted URI output
---
.../org/apache/jena/riot/out/quoted/QuotedURI.java | 312 ++++++++++++++++++++-
.../test/java/org/apache/jena/riot/out/TS_Out.java | 7 +-
...ringOutput.java => TestOutputQuotedString.java} | 38 ++-
.../apache/jena/riot/out/TestOutputQuotedURI.java | 84 ++++++
.../riot/out/quoted/TestQuotedURIInternal.java | 101 +++++++
.../java/org/apache/jena/atlas/lib/EscapeStr.java | 15 +-
6 files changed, 523 insertions(+), 34 deletions(-)
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/out/quoted/QuotedURI.java b/jena-arq/src/main/java/org/apache/jena/riot/out/quoted/QuotedURI.java
index 3d0292e4a7..89eef18626 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/out/quoted/QuotedURI.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/out/quoted/QuotedURI.java
@@ -19,31 +19,325 @@
package org.apache.jena.riot.out.quoted;
import org.apache.jena.atlas.io.AWriter ;
+import org.apache.jena.atlas.io.IndentedLineBuffer;
+import org.apache.jena.atlas.io.OutputUtils;
import org.apache.jena.atlas.lib.CharSpace ;
import org.apache.jena.atlas.lib.Chars ;
import org.apache.jena.atlas.lib.EscapeStr ;
+import org.apache.jena.atlas.logging.FmtLog;
+import org.apache.jena.riot.RiotException;
+import org.apache.jena.riot.SysRIOT;
+/**
+ * Writing URI strings for Turtle etc.
+ * <p>If the URI string contains bad characters (control characters
+ * x00 to x20 and characters <>"{}|^`\) various ways to handle this are provided.
+ * They cause either a different URI to written or an illegal one.
+ * <p>
+ * There is no way to
+ * write illegal characters. Percent-encoding is an encoding, not an escape
+ * mechanism. It put actual 3 characters %-X-X.into the URI.
+ */
public class QuotedURI {
+
private final CharSpace charSpace ;
public QuotedURI() {
this(CharSpace.UTF8) ;
}
-
+
+ @FunctionalInterface
+ private interface BadCharWriter { void writeChar(AWriter out, char ch); }
+
+ @FunctionalInterface
+ private interface BadCharHandler { void badChar(int idx, String str, char ch); }
+
public QuotedURI(CharSpace charSpace) {
- this.charSpace = charSpace ;
- }
-
+ this.charSpace = charSpace ;
+ }
+
/** Write a string for a URI on one line. */
public void writeURI(AWriter w, String s) {
- // URIs do not have an escape mechanism. %-is an encoding.
- // We can either print as-is or convert to %-encoding.
- // (Ignoring host names which be in puny code).
+ // URIs do not have an escape mechanism. %-is an encoding (it is a change to the URI characters).
+ //
+ // The grammar rule: '<' ([^#x00-#x20<>"{}|^`\] | UCHAR)* '>'
+
+ // The Turtle and SPARQL grammar rules are lax by design. They don't include the
+ // whole grammar of RFC 3986. Instead, they allow a range of characters to have
+ // a one line URI rule then require implementations check for legal URIs.
+
w.print(Chars.CH_LT);
if ( CharSpace.isAscii(charSpace) )
- EscapeStr.writeASCII(w, s) ;
+ EscapeStr.writeASCII(w, s);
else
- w.print(s) ;
+ systemWriteUnicodeURI(w, s);
w.print(Chars.CH_GT);
}
+
+ /** Write a URI string in UTF-8.
+ * This function is the policy for all Turtle/TriG/N-Tiples/N-Quads writing of URIs.
+ * <p>
+ * URIs strings sometimes contain bad characters.
+ * Control characters x00 to x20 and characters <>"{}|^`\ are mentioned
+ * in the grammars - Turtle et al., and SPARQL but there is also the requirement to be a legal URI
+ * according to <a href="https://www.w3.org/TR/rdf-concepts/#iri-abnf">ABNF for URIs</a>
+ * on top of the basic parsing rule.
+ * <p>
+ * Some control characters mess up the output: e.g. \n, \r, \f, BEL (U+0007) and others.
+ * <p>
+ * There are three choices:
+ * <ol>
+ * <li> Write whatever : Raw characters are emitted. {@link #writeRaw}.</li>
+ * <li> \-u control chars and bad characters. This meets the basic grammar requirements of Turtle/SPARQL
+ * but not the requirement to be a legal IRI : {@link #writeUnicodeEscapeBadChars}.</li>
+ * <li>Only worry about control characters, including ones that mess with the layout : {@link #writeUnicodeEscapeCtlChars}
+ * </li>
+ * <li>Warn about bad characters. {@link #writeWarnBadChars}</li>
+ * </ol>
+ *
+ * {@link #writeUnicodeEscapeCtlChars} is like {@link #writeUnicodeEscapeBadChars}
+ * except it only unicode-scapes control characters and lets characters, including space, through.
+ */
+ private static void systemWriteUnicodeURI(AWriter w, String s) {
+ // Write as-is
+ //writeDirect(w, s);
+
+ // Write with unicode UCHAR escapes. Legal by the grammar rule but still not a legal URI.
+ // All bad characters: Escape with \-u
+ writeUnicodeEscapeBadChars(w, s);
+ // Write with unicode UCHAR escapes just the control characters as UCHAR.
+ // writeUnicodeEscapeCtlChars(w, s);
+
+ // Percent encode. Makes a change of URI.
+ //writePercentEncodedeBadChars(w, s);
+
+ // warn-and-print and exception-or-write.
+ //writeWarnBadChars(w, s);
+ // writeExceptionOnBadChar(w,s);
+ }
+
+ // ---- Write choices.
+
+ /*package*/ static void writeDirect(AWriter out, String uriStr) {
+ out.print(uriStr);
+ }
+
+ /**
+ * Write, but check for bad characters. If there are any, warn but still output the string.
+ * Bad characters are those in the IRIREF productions:
+ * controls chars, and illegal chars - space and {@code <>"{}|^`\}
+ */
+ /*package*/ static void writeWarnBadChars(AWriter out, String uriStr) {
+ driverOnBadChars(out, uriStr, QuotedURI::warn);
+ }
+
+ /**
+ * Check for bad characters. If there are any, throw an exception with no output.
+ * Bad characters are those in the IRIREF productions:
+ * controls chars, and illegal chars - space and {@code <>"{}|^`\}
+ */
+ /*package*/ static void writeExceptionOnBadChar(AWriter out, String uriStr) {
+ driverOnBadChars(out, uriStr, QuotedURI::error);
+ }
+
+ /**
+ * Write, using Unicode escapes for controls chars, and illegal chars -
+ * space and <>"{}|^`\
+ * This function can write strings that have a different meaning.
+ * %-encoding is not an escape mechanism - there a really are three characters in the URI for %20
+ */
+ /*package*/ static void writeUnicodeEscapeBadChars(AWriter out, String uriStr) {
+ driverWriteBadChars(out, uriStr, QuotedURI::escapeUnicode);
+ }
+
+ /**
+ * Write, using Unicode escapes for controls chars, and illegal chars -
+ * space and <>"{}|^`\
+ * This function can write strings that have a different meaning.
+ * %-encoding is not an escape mechanism - there a really are three characters in the URI for %20
+ */
+ /*package*/ static void writePercentEncodedeBadChars(AWriter out, String uriStr) {
+ driverWriteBadChars(out, uriStr, QuotedURI::encodePercent);
+ }
+
+ /**
+ * Write, using Unicode escapes for controls chars - protects against layout characters.
+ * This function can write unparseable strings.
+ */
+ /*package*/ static void writeUnicodeEscapeCtlChars(AWriter out, String uriStr) {
+ driverWriteControlChars(out, uriStr, QuotedURI::escapeUnicode);
+ }
+
+ /**
+ * Check the string first character-by-character then directly print the whole
+ * string. The {@code BadCharHandler} can throw an exception, which terminates
+ * the function without any output; the exception is propagated.
+ */
+ private static void driverOnBadChars(AWriter out, String uriStr, BadCharHandler handler) {
+ int len = uriStr.length();
+ for (int i = 0; i < len; i++) {
+ char c = uriStr.charAt(i);
+ if ( isControlChar(c) ) {
+ handler.badChar(i, uriStr, c);
+ continue;
+ } else {
+ // And also <>"{}|^`\
+ switch(c) {
+ case ' ', '<', '>', '"', '{', '}', '|', '^', '`', '\\':
+ case '\u007F' : // DEL
+ handler.badChar(i, uriStr, c);
+ continue;
+ default:
+ }
+ }
+ }
+ // OK - print
+ out.print(uriStr);
+ }
+
+ /**
+ * Process the string character-by-character.
+ * Call a bad character handler on control or bad characters.
+ */
+ private static void driverWriteBadChars(AWriter out, String uriStr, BadCharWriter badCharWriter) {
+ int len = uriStr.length();
+ for (int i = 0; i < len; i++) {
+ char c = uriStr.charAt(i);
+ if ( isControlChar(c) ) {
+ badCharWriter.writeChar(out, c);
+ continue;
+ }
+ switch(c) {
+ case ' ', '<', '>', '"', '{', '}', '|', '^', '`', '\\':
+ case '\u007F' : // DEL
+ badCharWriter.writeChar(out, c);
+ break;
+ default:
+ out.print(c);
+ }
+ }
+ }
+
+ /**
+ * Process the string character-by-character.
+ * Call a bad character handler only on control characters.
+ */
+ private static void driverWriteControlChars(AWriter out, String uriStr, BadCharWriter escaper) {
+ int len = uriStr.length();
+ for (int i = 0; i < len; i++) {
+ char c = uriStr.charAt(i);
+ if ( isControlChar(c) ) {
+ escaper.writeChar(out, c);
+ continue;
+ }
+ }
+ }
+
+ private static void escapeUnicode(AWriter out, char c) {
+ out.print("\\u");
+ OutputUtils.printHex(out, c, 4);
+ }
+
+ private static void encodePercent(AWriter out, char c) {
+ if ( c <= 0xFF ) {
+ // One byte
+ out.print("%");
+ OutputUtils.printHex(out, c, 2);
+ return;
+ }
+ if ( c > 0xFF && c <= 0xFFFF) {
+ // 2 byte, Hi-Lo
+ int x = c;
+ out.print("%");
+ OutputUtils.printHex(out, x>>8, 2);
+ out.print("%");
+ OutputUtils.printHex(out, x|0xFF, 2);
+ }
+ throw new RiotException("Very bad character! "+Long.toHexString(c));
+ }
+
+ private static void warn(int idx, String str, char ch) {
+ String msg = formattedMessage(idx, str, ch);
+ FmtLog.warn(SysRIOT.getLogger(), msg);
+ }
+
+ private static void error(int idx, String str, char ch) {
+ String msg = formattedMessage(idx, str, ch);
+ throw new RiotException(msg);
+ }
+
+ private static String formattedMessage(int idx, String str, char ch) {
+ String chStr = Character.toString(ch);
+ if ( ch == ' ' )
+ chStr = " ";
+ if ( ch == '\t' )
+ chStr = "\\t";
+ if ( ch == '\n' )
+ chStr = "\\n";
+ str = displayUnicodeEscapeControlChars(str);
+ return String.format("Bad character in URI <%s> at position %d: '%s' codepoint U+%04X", str, idx, chStr, (int)ch);
+ }
+
+ // ---- Display helpers. Not for output.
+
+ /** Display form, indicating escape characters */
+ private static String displayUnicodeEscapeControlChars(String uriStr) {
+ IndentedLineBuffer out = new IndentedLineBuffer();
+ writeDisplayUnicodeControlChars(out, uriStr);
+ return out.asString();
+ }
+
+ /**
+ * Write, using Unicode codepoints for controls chars, and illegal chars -
+ * space and <>"{}|^`\
+ * This function creates displayable strings.
+ */
+ private static void writeDisplayUnicodeControlChars(AWriter out, String uriStr) {
+ int len = uriStr.length();
+ for (int i = 0; i < len; i++) {
+ char c = uriStr.charAt(i);
+ // Well-known characters
+ String s = displayUnicode(c);
+ if ( s != null ) {
+ out.print(s);
+ continue;
+ }
+ // Other control chars
+ if ( isControlChar(c) ) {
+ displayUnicodeEscape(out, c);
+ continue;
+ }
+ out.print(c);
+ }
+ }
+
+ // ---- Helper functions.
+
+ /**
+ * Control chars mentioned in the specs.
+ */
+ private static boolean isControlChar(char c) {
+ return c < 20 ;
+ // Unicode has a another control char block at 007F to 09FF
+ // In the range [U+0000, U+001F], or range [U+007F, U+009F]
+ //return Character.isISOControl(c);
+ }
+
+ private static String displayUnicode(char c) {
+ return switch (c) {
+ case '\n' -> "[NL]";
+ case '\r' -> "[CR]";
+ case '\f' -> "[FF]";
+ case ' ' -> "[space]";
+ case '\t' -> "[tab]";
+ // Other bad chars? <>"{}|^`\
+ default -> null;
+ };
+ }
+
+ private static void displayUnicodeEscape(AWriter out, char c) {
+ out.print("[U+");
+ OutputUtils.printHex(out, c, 4);
+ out.print("]");
+ }
}
diff --git a/jena-arq/src/test/java/org/apache/jena/riot/out/TS_Out.java b/jena-arq/src/test/java/org/apache/jena/riot/out/TS_Out.java
index f13735c4ab..447e87d0da 100644
--- a/jena-arq/src/test/java/org/apache/jena/riot/out/TS_Out.java
+++ b/jena-arq/src/test/java/org/apache/jena/riot/out/TS_Out.java
@@ -18,17 +18,18 @@
package org.apache.jena.riot.out;
+import org.apache.jena.riot.out.quoted.TestQuotedURIInternal;
import org.junit.runner.RunWith ;
import org.junit.runners.Suite ;
-
@RunWith(Suite.class)
@Suite.SuiteClasses( {
- TestQuotedStringOutput.class
+ TestOutputQuotedString.class
+ , TestOutputQuotedURI.class
+ , TestQuotedURIInternal.class
, TestNodeFmt.class
, TestNodeFmtLib.class
})
public class TS_Out
{}
-
diff --git a/jena-arq/src/test/java/org/apache/jena/riot/out/TestQuotedStringOutput.java b/jena-arq/src/test/java/org/apache/jena/riot/out/TestOutputQuotedString.java
similarity index 94%
rename from jena-arq/src/test/java/org/apache/jena/riot/out/TestQuotedStringOutput.java
rename to jena-arq/src/test/java/org/apache/jena/riot/out/TestOutputQuotedString.java
index 6336dddb9f..0267976a4d 100644
--- a/jena-arq/src/test/java/org/apache/jena/riot/out/TestQuotedStringOutput.java
+++ b/jena-arq/src/test/java/org/apache/jena/riot/out/TestOutputQuotedString.java
@@ -21,13 +21,13 @@ package org.apache.jena.riot.out;
import static org.junit.Assert.assertEquals ;
import org.apache.jena.atlas.io.StringWriterI ;
-import org.apache.jena.riot.out.quoted.QuotedStringOutput ;
-import org.apache.jena.riot.out.quoted.QuotedStringOutputNT ;
-import org.apache.jena.riot.out.quoted.QuotedStringOutputTTL ;
-import org.apache.jena.riot.out.quoted.QuotedStringOutputTTL_MultiLine ;
+import org.apache.jena.riot.out.quoted.QuotedStringOutput;
+import org.apache.jena.riot.out.quoted.QuotedStringOutputNT;
+import org.apache.jena.riot.out.quoted.QuotedStringOutputTTL;
+import org.apache.jena.riot.out.quoted.QuotedStringOutputTTL_MultiLine;
import org.junit.Test ;
-public class TestQuotedStringOutput {
+public class TestOutputQuotedString {
static void testSingleLine(QuotedStringOutput proc, String input, String expected) {
StringWriterI w = new StringWriterI() ;
@@ -36,62 +36,60 @@ public class TestQuotedStringOutput {
expected = proc.getQuoteChar()+expected+proc.getQuoteChar() ;
assertEquals(expected, output) ;
}
-
+
static void testMultiLine(QuotedStringOutput proc, String input, String expected) {
StringWriterI w = new StringWriterI() ;
proc.writeStrMultiLine(w, input);
String output = w.toString() ;
assertEquals(expected, output) ;
}
-
+
private QuotedStringOutput escProcNT = new QuotedStringOutputNT() ;
private QuotedStringOutput escProcTTL_S2 = new QuotedStringOutputTTL('"') ;
private QuotedStringOutput escProcTTL_S1 = new QuotedStringOutputTTL('\'') ;
-
+
private QuotedStringOutput escProcTTL_M2 = new QuotedStringOutputTTL_MultiLine('"') ;
private QuotedStringOutput escProcTTL_M1 = new QuotedStringOutputTTL_MultiLine('\'') ;
-
+
@Test public void escape_nt_00() { testSingleLine(escProcNT, "", "") ; }
@Test public void escape_nt_01() { testSingleLine(escProcNT, "abc", "abc") ; }
@Test public void escape_nt_02() { testSingleLine(escProcNT, "abc\ndef", "abc\\ndef") ; }
-
+
@Test public void escape_nt_03() { testSingleLine(escProcNT, "\"", "\\\"") ; }
@Test public void escape_nt_04() { testSingleLine(escProcNT, "'", "'") ; }
@Test public void escape_nt_05() { testSingleLine(escProcNT, "xyz\t", "xyz\\t") ; }
-
+
@Test public void escape_ttl_singleline_quote2_00() { testSingleLine(escProcTTL_S2, "", "") ; }
@Test public void escape_ttl_singleline_quote2_01() { testSingleLine(escProcTTL_S2, "abc", "abc") ; }
@Test public void escape_ttl_singleline_quote2_02() { testSingleLine(escProcTTL_S2, "abc\ndef", "abc\\ndef") ; }
-
+
@Test public void escape_ttl_singleline_quote2_03() { testSingleLine(escProcTTL_S2, "\"", "\\\"") ; }
@Test public void escape_ttl_singleline_quote2_04() { testSingleLine(escProcTTL_S2, "'", "'") ; }
@Test public void escape_ttl_singleline_quote2_05() { testSingleLine(escProcTTL_S2, "xyz\t", "xyz\\t") ; }
-
+
@Test public void escape_ttl_singleline_quote1_00() { testSingleLine(escProcTTL_S1, "", "") ; }
@Test public void escape_ttl_singleline_quote1_01() { testSingleLine(escProcTTL_S1, "abc", "abc") ; }
@Test public void escape_ttl_singleline_quote1_02() { testSingleLine(escProcTTL_S1, "abc\ndef", "abc\\ndef") ; }
-
+
@Test public void escape_ttl_singleline_quote1_03() { testSingleLine(escProcTTL_S1, "\"", "\"") ; }
@Test public void escape_ttl_singleline_quote1_04() { testSingleLine(escProcTTL_S1, "'", "\\'") ; }
@Test public void escape_ttl_singleline_quote1_05() { testSingleLine(escProcTTL_S1, "xyz\t", "xyz\\t") ; }
-
+
// Multiple with single line output processor.
@Test public void escape_ttl_singleline_quote2_multiline_str0() { testMultiLine(escProcTTL_S2, "", "\"\"") ; }
@Test public void escape_ttl_singleline_quote2_multiline_str1() { testMultiLine(escProcTTL_S2, "abc", "\"abc\"") ; }
@Test public void escape_ttl_singleline_quote2_multiline_str2() { testMultiLine(escProcTTL_S2, "abc\ndef", "\"abc\\ndef\"") ; }
-
+
@Test public void escape_ttl_singleline_quote2_multiline_str3() { testMultiLine(escProcTTL_S2, "\"", "\"\\\"\"") ; }
@Test public void escape_ttl_singleline_quote2_multiline_str4() { testMultiLine(escProcTTL_S2, "'", "\"'\"") ; }
@Test public void escape_ttl_singleline_quote2_multiline_str5() { testMultiLine(escProcTTL_S2, "xyz\t", "\"xyz\\t\"") ; }
-
+
// Multiple with single line output processor.
@Test public void escape_ttl_singleline_quote1_multiline_str0() { testMultiLine(escProcTTL_S1, "", "''") ; }
@Test public void escape_ttl_singleline_quote1_multiline_str1() { testMultiLine(escProcTTL_S1, "abc", "'abc'") ; }
@Test public void escape_ttl_singleline_quote1_multiline_str2() { testMultiLine(escProcTTL_S1, "abc\ndef", "'abc\\ndef'") ; }
-
+
@Test public void escape_ttl_singleline_quote1_multiline_str3() { testMultiLine(escProcTTL_S1, "\"", "'\"'") ; }
@Test public void escape_ttl_singleline_quote1_multiline_str4() { testMultiLine(escProcTTL_S1, "'", "'\\''") ; }
@Test public void escape_ttl_singleline_quote1_multiline_str5() { testMultiLine(escProcTTL_S1, "xyz\t", "'xyz\\t'") ; }
-
- // Multiline, multiline
}
diff --git a/jena-arq/src/test/java/org/apache/jena/riot/out/TestOutputQuotedURI.java b/jena-arq/src/test/java/org/apache/jena/riot/out/TestOutputQuotedURI.java
new file mode 100644
index 0000000000..3adc0cd1a5
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/riot/out/TestOutputQuotedURI.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.jena.riot.out;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.jena.atlas.io.IndentedLineBuffer;
+import org.apache.jena.atlas.logging.LogCtl;
+import org.apache.jena.riot.SysRIOT;
+import org.apache.jena.riot.out.quoted.QuotedURI;
+import org.apache.jena.riot.out.quoted.TestQuotedURIInternal;
+import org.junit.Test;
+
+/**
+ * Tests for URI output.
+ * See also {@link TestQuotedURIInternal} for other tests that call the implementation options.
+ */
+public class TestOutputQuotedURI {
+
+ @Test public void quoted_uri_api_1() {
+ testQuotedURI("http://example/", "<http://example/>", true);
+ }
+
+ @Test public void quoted_uri_api_2() {
+ testQuotedURI("http://example/αβγ", "<http://example/αβγ>", true);
+ }
+
+ @Test public void quoted_uri_api_3() {
+ testQuotedURI("http://example/١٢٣", "<http://example/١٢٣>", true);
+ }
+
+ @Test public void quoted_uri_api_encoding_1() {
+ // If percent encoding. Space in, something out.
+ //testQuotedURI("http://example/abc def", "<http://example/abc%20def>", false);
+ // If Unicode UCHAR escaping. Note double \\ - use Java escape to get a single \ into the string.
+ testQuotedURI("http://example/abc def", "<http://example/abc\\u0020def>", false);
+ }
+
+ @Test public void quoted_uri_api_encoding_2() {
+ // Test encoding setup. Raw tab in, something for the tab out. Tab is a control character
+ // If percent encoding.
+ //testQuotedURI("http://example/abc\u0009def", "<http://example/abc%09def>", false);
+ // If Unicode UCHAR escaping.
+ testQuotedURI("http://example/abc\u0009def", "<http://example/abc\\u0009def>", false);
+ }
+
+ @Test public void quoted_uri_api_encoding_3() {
+ // Test encoding setup. Illegal {} in the URI string
+ // If percent encoding.
+ //testQuotedURI("http://example/_{_{}_", "<http://example/_%7B_7D_>", false);
+ // If Unicode UCHAR escaping.
+ testQuotedURI("http://example/_{_}_", "<http://example/_\\u007B_\\u007D_>", false);
+ }
+
+ private static void testQuotedURI(String input, String expected, boolean withWarnings) {
+ Runnable r = ()->{
+ QuotedURI quoter = new QuotedURI();
+ IndentedLineBuffer x = new IndentedLineBuffer();
+ quoter.writeURI(x, input);
+ String s = x.asString();
+ assertEquals(expected, s);
+ };
+ if ( withWarnings )
+ r.run();
+ else
+ LogCtl.withLevel(SysRIOT.getLogger(), "ERROR", r);
+ }
+}
diff --git a/jena-arq/src/test/java/org/apache/jena/riot/out/quoted/TestQuotedURIInternal.java b/jena-arq/src/test/java/org/apache/jena/riot/out/quoted/TestQuotedURIInternal.java
new file mode 100644
index 0000000000..41f5593c70
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/riot/out/quoted/TestQuotedURIInternal.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.jena.riot.out.quoted;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.function.BiConsumer;
+
+import org.apache.jena.atlas.io.AWriter;
+import org.apache.jena.atlas.io.IndentedLineBuffer;
+import org.apache.jena.riot.RiotException;
+import org.apache.jena.riot.out.TestOutputQuotedURI;
+import org.junit.Test;
+
+/**
+ * Tests for the different options in QuoteURI.
+ * {@link TestOutputQuotedURI} tests the API.
+ */
+public class TestQuotedURIInternal {
+ // Must be in the same package as QuotedURI.
+
+ // Normal
+ @Test
+ public void write_uri_11() {
+ testQuotedURI("http://example/", "http://example/", QuotedURI::writeDirect);
+ }
+
+ @Test
+ public void write_uri_12() {
+ testQuotedURI("http://example/", "http://example/", QuotedURI::writeUnicodeEscapeBadChars);
+ }
+
+ @Test
+ public void write_uri_13() {
+ testQuotedURI("http://example/", "http://example/", QuotedURI::writeExceptionOnBadChar);
+ }
+
+ @Test
+ public void write_uri_21() {
+ testQuotedURI("http://example/αβγ", "http://example/αβγ", QuotedURI::writeDirect);
+ }
+
+ @Test
+ public void write_uri_22() {
+ testQuotedURI("http://example/αβγ", "http://example/αβγ", QuotedURI::writeUnicodeEscapeBadChars);
+ }
+
+ @Test
+ public void write_uri_23() {
+ testQuotedURI("http://example/αβγ", "http://example/αβγ", QuotedURI::writeExceptionOnBadChar);
+ }
+
+ // Error cases
+ @Test
+ public void write_uri_51() {
+ testQuotedURI("http://example/abc def", "http://example/abc def", QuotedURI::writeDirect);
+ }
+
+ @Test
+ public void write_uri_52() {
+ testQuotedURI("http://example/abc def", "http://example/abc\\u0020def", QuotedURI::writeUnicodeEscapeBadChars);
+ }
+
+ @Test
+ public void write_uri_61() {
+ testQuotedURI("http://example/abc{}def", "http://example/abc\\u007B\\u007Ddef", QuotedURI::writeUnicodeEscapeBadChars);
+ }
+
+ @Test
+ public void write_uri_62() {
+ testQuotedURI("http://example/abc{}def", "http://example/abc%7B%7Ddef", QuotedURI::writePercentEncodedeBadChars);
+ }
+
+ @Test(expected=RiotException.class)
+ public void write_uri_71() {
+ testQuotedURI("http://example/abc def", "http://example/abc", QuotedURI::writeExceptionOnBadChar);
+ }
+
+ private static void testQuotedURI(String input, String expected, BiConsumer<AWriter, String> quoteOperation) {
+ IndentedLineBuffer x = new IndentedLineBuffer();
+ quoteOperation.accept(x, input);
+ String s = x.asString();
+ assertEquals(expected, s);
+ }
+}
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/EscapeStr.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/EscapeStr.java
index f67b4d861f..e40452b816 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/lib/EscapeStr.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/EscapeStr.java
@@ -27,11 +27,22 @@ import org.apache.jena.atlas.io.StringWriterI ;
public class EscapeStr
{
/*
- * Escape characters in a string according to Turtle rules.
+ * Escape characters in a string according to Turtle rules
+ * for a single line string, using double quotes as the string delimiters
+ * Delimiters are not included in the result.
*/
public static String stringEsc(String s) {
+ return stringEsc(s, Chars.CH_QUOTE2);
+ }
+
+ /*
+ * Escape characters in a string according to Turtle rules,
+ * where {@code quoteChar} is the delimiter.
+ * Delimiters are not included in the result.
+ */
+ public static String stringEsc(String s, char quoteChar) {
AWriter w = new StringWriterI() ;
- stringEsc(w, s, Chars.CH_QUOTE2, true, CharSpace.UTF8) ;
+ stringEsc(w, s, quoteChar, true, CharSpace.UTF8) ;
return w.toString() ;
}