From 48d480d6dafa1e87ac9ad210a852a778aa5f973b Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 17 Dec 2025 10:52:51 +0100 Subject: [PATCH 1/2] Case insensitive relation check, fixes #17 --- .../java/org/z3950/zing/cql/CQLParser.java | 49 +++++++++---------- src/test/resources/regression/02/03.cql | 2 +- src/test/resources/regression/02/03.xcql | 2 +- src/test/resources/regression/09/06.cql | 2 +- src/test/resources/regression/09/06.xcql | 8 +-- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/z3950/zing/cql/CQLParser.java b/src/main/java/org/z3950/zing/cql/CQLParser.java index 93c7c10..788ecd8 100644 --- a/src/main/java/org/z3950/zing/cql/CQLParser.java +++ b/src/main/java/org/z3950/zing/cql/CQLParser.java @@ -282,40 +282,39 @@ private CQLNode parsePrefix(String index, CQLRelation relation, return new CQLPrefixNode(name, identifier, node); } - + private boolean isWordOrString() { - return CQLTokenizer.TT_WORD == lexer.what() + return CQLTokenizer.TT_WORD == lexer.what() || CQLTokenizer.TT_STRING == lexer.what(); } private boolean isRelation() { - debug("isRelation: checking what()=" + lexer.what() + - " (" + lexer.render() + ")"); - if (lexer.what() == CQLTokenizer.TT_WORD && - (lexer.value().indexOf('.') >= 0 || - lexer.value().equals("any") || - lexer.value().equals("all") || - lexer.value().equals("within") || - lexer.value().equals("encloses") || - (lexer.value().equals("exact") && compat != V1POINT2) || - (lexer.value().equals("scr") && compat != V1POINT2) || - (lexer.value().equals("adj") && compat == V1POINT2) || - customRelations.contains(lexer.value()))) - return true; - + debug("isRelation: checking what()=" + lexer.what() + + " (" + lexer.render() + ")"); + if (lexer.what() == CQLTokenizer.TT_WORD) { + return lexer.value().indexOf('.') >= 0 || + lexer.value().equalsIgnoreCase("any") || + lexer.value().equalsIgnoreCase("all") || + lexer.value().equalsIgnoreCase("within") || + lexer.value().equalsIgnoreCase("encloses") || + (lexer.value().equalsIgnoreCase("exact") && compat != V1POINT2) || + (lexer.value().equalsIgnoreCase("scr") && compat != V1POINT2) || + (lexer.value().equalsIgnoreCase("adj") && compat == V1POINT2) || + customRelations.stream().anyMatch(r -> r.equalsIgnoreCase(lexer.value())); + } return isSymbolicRelation(); } private boolean isSymbolicRelation() { - debug("isSymbolicRelation: checking what()=" + lexer.what() + - " (" + lexer.render() + ")"); - return (lexer.what() == '<' || - lexer.what() == '>' || - lexer.what() == '=' || - lexer.what() == CQLTokenizer.TT_LE || - lexer.what() == CQLTokenizer.TT_GE || - lexer.what() == CQLTokenizer.TT_NE || - lexer.what() == CQLTokenizer.TT_EQEQ); + debug("isSymbolicRelation: checking what()=" + lexer.what() + + " (" + lexer.render() + ")"); + return (lexer.what() == '<' || + lexer.what() == '>' || + lexer.what() == '=' || + lexer.what() == CQLTokenizer.TT_LE || + lexer.what() == CQLTokenizer.TT_GE || + lexer.what() == CQLTokenizer.TT_NE || + lexer.what() == CQLTokenizer.TT_EQEQ); } private void match(int token) diff --git a/src/test/resources/regression/02/03.cql b/src/test/resources/regression/02/03.cql index 553d2b8..b469c5f 100644 --- a/src/test/resources/regression/02/03.cql +++ b/src/test/resources/regression/02/03.cql @@ -1 +1 @@ -title any fish \ No newline at end of file +title Any fish diff --git a/src/test/resources/regression/02/03.xcql b/src/test/resources/regression/02/03.xcql index 62521be..8034b6a 100644 --- a/src/test/resources/regression/02/03.xcql +++ b/src/test/resources/regression/02/03.xcql @@ -1,7 +1,7 @@ title - any + Any fish diff --git a/src/test/resources/regression/09/06.cql b/src/test/resources/regression/09/06.cql index c1c8282..9e80bcb 100644 --- a/src/test/resources/regression/09/06.cql +++ b/src/test/resources/regression/09/06.cql @@ -1 +1 @@ -sortby sortby sortby sortby sortby \ No newline at end of file +Sortby Sortby Sortby Sortby Sortby \ No newline at end of file diff --git a/src/test/resources/regression/09/06.xcql b/src/test/resources/regression/09/06.xcql index 2e136b5..337cd16 100644 --- a/src/test/resources/regression/09/06.xcql +++ b/src/test/resources/regression/09/06.xcql @@ -3,16 +3,16 @@ = - sortby + Sortby - sortby + Sortby - sortby + Sortby - sortby + Sortby From 8a7fbe0ddddd3b9f3737a64a7830a8c3ef631462 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Wed, 17 Dec 2025 11:31:17 +0100 Subject: [PATCH 2/2] Custom relations case-insensitive --- .../java/org/z3950/zing/cql/CQLParser.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/z3950/zing/cql/CQLParser.java b/src/main/java/org/z3950/zing/cql/CQLParser.java index 788ecd8..9256eef 100644 --- a/src/main/java/org/z3950/zing/cql/CQLParser.java +++ b/src/main/java/org/z3950/zing/cql/CQLParser.java @@ -21,7 +21,7 @@ public class CQLParser { private CQLTokenizer lexer; private final int compat; // When false, implement CQL 1.2 private final Set customRelations = new HashSet(); - + public static final int V1POINT1 = 12368; public static final int V1POINT2 = 12369; public static final int V1POINT1SORT = 12370; @@ -47,10 +47,10 @@ public CQLParser(int compat) { this.compat = compat; this.allowKeywordTerms = true; } - + /** - * Official CQL grammar allows registered keywords like 'and/or/not/sortby/prox' - * to be used unquoted in terms. This constructor allows to create an instance + * Official CQL grammar allows registered keywords like 'and/or/not/sortby/prox' + * to be used unquoted in terms. This constructor allows to create an instance * of a parser that prohibits this behavior while sacrificing compatibility. * @param compat CQL version compatibility * @param allowKeywordTerms when false registered keywords are disallowed in unquoted terms @@ -59,7 +59,7 @@ public CQLParser(int compat, boolean allowKeywordTerms) { this.compat = compat; this.allowKeywordTerms = allowKeywordTerms; } - + /** * The new parser implements CQL 1.2 */ @@ -72,24 +72,25 @@ private static void debug(String str) { if (DEBUG) System.err.println("PARSEDEBUG: " + str); } - + /** * Registers custom relation in this parser. Note that when a custom relation * is registered the parser is no longer strictly compliant with the chosen spec. + * Custom relations are case-insensitive. * @param relation * @return true if custom relation has not been registered already */ public boolean registerCustomRelation(String relation) { - return customRelations.add(relation); + return customRelations.add(relation.toLowerCase()); } - + /** * Unregisters previously registered custom relation in this instance of the parser. * @param relation * @return true is relation has been previously registered */ public boolean unregisterCustomRelation(String relation) { - return customRelations.remove(relation); + return customRelations.remove(relation.toLowerCase()); } /** @@ -117,7 +118,7 @@ public CQLNode parse(String cql) CQLNode root = parseTopLevelPrefixes("cql.serverChoice", new CQLRelation(compat == V1POINT2 ? "=" : "scr")); if (lexer.what() != CQLTokenizer.TT_EOF) - throw new CQLParseException("junk after end: " + lexer.render(), + throw new CQLParseException("junk after end: " + lexer.render(), lexer.pos()); return root; @@ -194,7 +195,7 @@ private ModifierSet gatherModifiers(String base) match('/'); if (lexer.what() != CQLTokenizer.TT_WORD) throw new CQLParseException("expected modifier, " - + "got " + lexer.render(), + + "got " + lexer.render(), lexer.pos()); String type = lexer.value().toLowerCase(); match(lexer.what()); @@ -241,7 +242,7 @@ private CQLNode parseTerm(String index, CQLRelation relation) if (!isRelation()) break; //we're done if no relation - + //render relation String relstr = (lexer.what() == CQLTokenizer.TT_WORD ? lexer.value() : lexer.render(lexer.what(), false)); @@ -323,7 +324,7 @@ private void match(int token) if (lexer.what() != token) throw new CQLParseException("expected " + lexer.render(token, true) + - ", " + "got " + lexer.render(), + ", " + "got " + lexer.render(), lexer.pos()); lexer.move(); debug("match() got token=" + lexer.what() + ", value()='" + lexer.value() + "'"); @@ -499,7 +500,7 @@ public static void main (String[] args) { f.close(); System.out.println(root.toPQF(config)); } catch (IOException ex) { - System.err.println("Can't load PQF properties:" + + System.err.println("Can't load PQF properties:" + ex.getMessage()); System.exit(5); }