Flag to control if keyword terms are allowed
[cql-java-moved-to-github.git] / src / main / java / org / z3950 / zing / cql / CQLParser.java
index 2509563..d577cd9 100644 (file)
@@ -8,7 +8,9 @@ import java.io.FileNotFoundException;
 import java.io.Reader;
 import java.io.StringReader;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 
 /**
@@ -20,10 +22,13 @@ import java.util.List;
 public class CQLParser {
     private CQLLexer lexer;
     private PositionAwareReader par; //active reader with position
-    private int compat;        // When false, implement CQL 1.2
+    private final int compat;  // When false, implement CQL 1.2
+    private final Set<String> customRelations = new HashSet<String>();
+    
     public static final int V1POINT1 = 12368;
     public static final int V1POINT2 = 12369;
     public static final int V1POINT1SORT = 12370;
+    public final boolean allowKeywordTerms;
 
     static private boolean DEBUG = false;
     static private boolean LEXDEBUG = false;
@@ -43,13 +48,27 @@ public class CQLParser {
      */
     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 
+     * 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
+     */
+    public CQLParser(int compat, boolean allowKeywordTerms) {
+       this.compat = V1POINT2;
+        this.allowKeywordTerms = allowKeywordTerms;
+    }
+    
     /**
      * The new parser implements CQL 1.2
      */
     public CQLParser() {
        this.compat = V1POINT2;
+        this.allowKeywordTerms = true;
     }
 
     private static void debug(String str) {
@@ -58,6 +77,25 @@ public class CQLParser {
     }
     
     /**
+     * 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.
+     * @param relation
+     * @return true if custom relation has not been registered already
+     */
+    public boolean registerCustomRelation(String relation) {
+      return customRelations.add(relation);
+    }
+    
+    /**
+     * 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);
+    }
+    
+    /**
      * Compiles a CQL query.
      * <P>
      * The resulting parse tree may be further processed by hand (see
@@ -272,7 +310,8 @@ public class CQLParser {
              lexer.sval.equals("encloses") ||
              (lexer.sval.equals("exact") && compat != V1POINT2) ||
              (lexer.sval.equals("scr") && compat != V1POINT2) ||
-             (lexer.sval.equals("adj") && compat == V1POINT2)))
+             (lexer.sval.equals("adj") && compat == V1POINT2) ||
+             customRelations.contains(lexer.sval)))
           return true;
 
         return isSymbolicRelation();
@@ -316,11 +355,12 @@ public class CQLParser {
            // indexes, terms, prefix names and prefix identifiers.
            // ### Instead, we should ask the lexer whether what we
            // have is a keyword, and let the knowledge reside there.
+            (allowKeywordTerms &&
            lexer.ttype == CQLLexer.TT_AND ||
            lexer.ttype == CQLLexer.TT_OR ||
            lexer.ttype == CQLLexer.TT_NOT ||
            lexer.ttype == CQLLexer.TT_PROX ||
-           lexer.ttype == CQLLexer.TT_SORTBY) {
+           lexer.ttype == CQLLexer.TT_SORTBY)) {
            String symbol = (lexer.ttype == CQLLexer.TT_NUMBER) ?
                lexer.render() : lexer.sval;
            match(lexer.ttype);