diff --git a/src/main/java/org/z3950/zing/cql/CQLLexer.java b/src/main/java/org/z3950/zing/cql/CQLLexer.java index c27ba40..a598b01 100644 --- a/src/main/java/org/z3950/zing/cql/CQLLexer.java +++ b/src/main/java/org/z3950/zing/cql/CQLLexer.java @@ -18,6 +18,8 @@ public class CQLLexer implements CQLTokenizer { private String lval; private StringBuilder buf = new StringBuilder(); + static final String OPS_AND_WHITESPACE = "()/<>= \t\r\n"; + public CQLLexer(String cql, boolean debug) { qs = cql; ql = cql.length(); @@ -89,8 +91,7 @@ public void move() { } else { what = TT_WORD; buf.setLength(0); // reset buffer - while (qi < ql - && !strchr("()/<>= \t\r\n", qs.charAt(qi))) { + while (qi < ql && !strchr(OPS_AND_WHITESPACE, qs.charAt(qi))) { buf.append(qs.charAt(qi)); qi++; } diff --git a/src/main/java/org/z3950/zing/cql/CQLTermNode.java b/src/main/java/org/z3950/zing/cql/CQLTermNode.java index 9e6551d..13a26c8 100644 --- a/src/main/java/org/z3950/zing/cql/CQLTermNode.java +++ b/src/main/java/org/z3950/zing/cql/CQLTermNode.java @@ -78,8 +78,8 @@ void toXCQLInternal(XCQLBuilder b, int level, List prefixes, @Override public String toCQL() { - String quotedIndex = maybeQuote(index); - String quotedTerm = maybeQuote(term); + String quotedIndex = toCQLTerm(index); + String quotedTerm = toCQLTerm(term); String res = quotedTerm; if (index != null && @@ -192,7 +192,7 @@ public String toPQF(Properties config) throws PQFTranslationException { if (isResultSetIndex(index)) { // Special case: ignore relation, modifiers, wildcards, etc. // There's parallel code in toType1BER() - return "@set " + maybeQuote(term); + return "@set " + toCQLTerm(term); } List attrs = getAttrs(config); @@ -219,28 +219,38 @@ public String toPQF(Properties config) throws PQFTranslationException { text = text.substring(0, len - 1); } - return s + maybeQuote(text); + return s + toCQLTerm(text); } - static String maybeQuote(String str) { - if (str == null) + // ensure that a term is properly quoted for CQL output if necessary. + // If the term has a bare double-quote (") it will be + // escaped with a backslash. + static String toCQLTerm(String str) { + if (str == null) { return null; - - // There _must_ be a better way to make this test ... - if (str.length() == 0 || - str.indexOf('"') != -1 || - str.indexOf(' ') != -1 || - str.indexOf('\t') != -1 || - str.indexOf('=') != -1 || - str.indexOf('<') != -1 || - str.indexOf('>') != -1 || - str.indexOf('/') != -1 || - str.indexOf('(') != -1 || - str.indexOf(')') != -1) { - str = '"' + str.replaceAll("(?= 0) { + quote = true; + } + if (ch == '"' && !escaped) { + sb.append('\\'); + } + escaped = ch == '\\' && !escaped; + sb.append(ch); + } + if (escaped) { + // trailing backslash - escape it + sb.append('\\'); + } + if (quote) { + return "\"" + sb.toString() + "\""; + } else { + return sb.toString(); + } } @Override diff --git a/src/test/java/org/z3950/zing/cql/CQLTermNodeTest.java b/src/test/java/org/z3950/zing/cql/CQLTermNodeTest.java new file mode 100644 index 0000000..760c1fa --- /dev/null +++ b/src/test/java/org/z3950/zing/cql/CQLTermNodeTest.java @@ -0,0 +1,62 @@ +package org.z3950.zing.cql; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class CQLTermNodeTest { + @Test + public void TestTCQLTermQuoteNull() { + assertNull(CQLTermNode.toCQLTerm(null)); + } + + @Test + public void TestTCQLTermQuoteEmpty() { + assertEquals("\"\"", CQLTermNode.toCQLTerm("")); + } + + @Test + public void TestTCQLTermQuoteRelation() { + assertEquals("\"<\"", CQLTermNode.toCQLTerm("<")); + } + + @Test + public void TestTCQLTermQuoteSimple() { + assertEquals("simple", CQLTermNode.toCQLTerm("simple")); + } + + @Test + public void TestTCQLTermQuoteBlank() { + assertEquals("\"a b\"", CQLTermNode.toCQLTerm("a b")); + } + + @Test + public void TestTCQLTermQuoteQuote1() { + assertEquals("a\\\"", CQLTermNode.toCQLTerm("a\"")); + } + + @Test + public void TestTCQLTermQuoteQuote2() { + assertEquals("a\\\"", CQLTermNode.toCQLTerm("a\\\"")); + } + + @Test + public void TestTCQLTermQuoteQuote3() { + assertEquals("a" + "\\\\" + "\\\"", CQLTermNode.toCQLTerm("a" + "\\\\" + "\"")); + } + + @Test + public void TestTCQLTermQuoteQuote4() { + assertEquals("a" + "\\\\" + "\\\"", CQLTermNode.toCQLTerm("a" + "\\\\" + "\\\"")); + } + + @Test + public void TestTCQLTermQuoteBackSlashTrail1() { + assertEquals("a\\\\", CQLTermNode.toCQLTerm("a\\")); + } + + @Test + public void TestTCQLTermQuoteBackSlashTrail2() { + assertEquals("\"a \\\\\"", CQLTermNode.toCQLTerm("a \\")); + } + +}