Towards 0.2 --
[cql-java-moved-to-github.git] / src / org / z3950 / zing / cql / CQLParser.java
index 0aded47..59cedf8 100644 (file)
@@ -1,14 +1,18 @@
-// $Id: CQLParser.java,v 1.11 2002-10-31 22:22:01 mike Exp $
+// $Id: CQLParser.java,v 1.15 2002-11-06 00:05:58 mike Exp $
 
 package org.z3950.zing.cql;
 import java.io.IOException;
+import java.util.Vector;
+import java.util.Properties;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 
 
 /**
- * Compiles a CQL string into a parse tree.
- * ##
+ * Compiles CQL strings into parse trees of CQLNode subtypes.
  *
- * @version    $Id: CQLParser.java,v 1.11 2002-10-31 22:22:01 mike Exp $
+ * @version    $Id: CQLParser.java,v 1.15 2002-11-06 00:05:58 mike Exp $
  * @see                <A href="http://zing.z3950.org/cql/index.html"
  *                     >http://zing.z3950.org/cql/index.html</A>
  */
@@ -22,6 +26,20 @@ public class CQLParser {
            System.err.println("PARSEDEBUG: " + str);
     }
 
+    /**
+     * Compiles a CQL query.
+     * <P>
+     * The resulting parse tree may be further processed by hand (see
+     * the individual node-types' documentation for details on the
+     * data structure) or, more often, simply rendered out in the
+     * desired form using one of the back-ends.  <TT>toCQL()</TT>
+     * returns a decompiled CQL query equivalent to the one that was
+     * compiled in the first place; and <TT>toXCQL()</TT> returns an
+     * XML snippet representing the query.
+     *
+     * @param cql      The query
+     * @return         A CQLNode object which is the root of a parse
+     * tree representing the query.  */
     public CQLNode parse(String cql)
        throws CQLParseException, IOException {
        lexer = new CQLLexer(cql, LEXDEBUG);
@@ -83,13 +101,19 @@ public class CQLParser {
                CQLNode expr = parse_query(qualifier, relation);
                match(')');
                return expr;
-           } else if (lexer.ttype != lexer.TT_WORD && lexer.ttype != '"') {
+           } else if (lexer.ttype != lexer.TT_WORD &&
+                      lexer.ttype != lexer.TT_NUMBER &&
+                      lexer.ttype != '"') {
                throw new CQLParseException("expected qualifier or term, " +
                                            "got " + lexer.render());
            }
 
            debug("non-parenthesised term");
-           word = lexer.sval;
+           if (lexer.ttype == lexer.TT_NUMBER) {
+               word = lexer.render();
+           } else {
+               word = lexer.sval;
+           }
            match(lexer.ttype);
            if (!isBaseRelation())
                break;
@@ -100,12 +124,13 @@ public class CQLParser {
 
            while (lexer.ttype == '/') {
                match('/');
-               // ### could insist on known modifiers only
-               if (lexer.ttype != lexer.TT_WORD)
+               if (lexer.ttype != lexer.TT_RELEVANT &&
+                   lexer.ttype != lexer.TT_FUZZY &&
+                   lexer.ttype != lexer.TT_STEM)
                    throw new CQLParseException("expected relation modifier, "
                                                + "got " + lexer.render());
                relation.addModifier(lexer.sval);
-               match(lexer.TT_WORD);
+               match(lexer.ttype);
            }
 
            debug("qualifier='" + qualifier + ", " +
@@ -127,7 +152,8 @@ public class CQLParser {
            if (lexer.ttype != '/') {
                // not an omitted default
                switch (i) {
-                   // Assumes order is: relation/distance/unit/ordering
+                   // Order should be: relation/distance/unit/ordering
+                   // For now, use MA's: unit/relation/distance/ordering
                case 0: gatherProxRelation(node); break;
                case 1: gatherProxDistance(node); break;
                case 2: gatherProxUnit(node); break;
@@ -179,7 +205,7 @@ public class CQLParser {
        match(lexer.ttype);
     }
 
-    boolean isBaseRelation() {
+    private boolean isBaseRelation() {
        debug("isBaseRelation: checking ttype=" + lexer.ttype +
              " (" + lexer.render() + ")");
        return (isProxRelation() ||
@@ -188,7 +214,7 @@ public class CQLParser {
                lexer.ttype == lexer.TT_EXACT);
     }
 
-    boolean isProxRelation() {
+    private boolean isProxRelation() {
        debug("isProxRelation: checking ttype=" + lexer.ttype +
              " (" + lexer.render() + ")");
        return (lexer.ttype == '<' ||
@@ -213,43 +239,90 @@ public class CQLParser {
     }
 
 
-    // Test harness.
-    //
-    // e.g. echo '(au=Kerninghan or au=Ritchie) and ti=Unix' |
-    //                         java org.z3950.zing.cql.CQLParser
-    // yields:
-    // <triple>
-    //   <boolean>and</boolean>
-    //   <triple>
-    //     <boolean>or</boolean>
-    //     <searchClause>
-    //       <index>au<index>
-    //       <relation>=<relation>
-    //       <term>Kerninghan<term>
-    //     </searchClause>
-    //     <searchClause>
-    //       <index>au<index>
-    //       <relation>=<relation>
-    //       <term>Ritchie<term>
-    //     </searchClause>
-    //   </triple>
-    //   <searchClause>
-    //     <index>ti<index>
-    //     <relation>=<relation>
-    //     <term>Unix<term>
-    //   </searchClause>
-    // </triple>
-    //
+    /**
+     * Simple test-harness for the CQLParser class.
+     * <P>
+     * Reads a CQL query either from its command-line argument, if
+     * there is one, or standard input otherwise.  So these two
+     * invocations are equivalent:
+     * <PRE>
+     *  CQLParser 'au=(Kerninghan or Ritchie) and ti=Unix'
+     *  echo au=(Kerninghan or Ritchie) and ti=Unix | CQLParser
+     * </PRE>
+     * The test-harness parses the supplied query and renders is as
+     * XCQL, so that both of the invocations above produce the
+     * following output:
+     * <PRE>
+     * &lt;triple&gt;
+     *   &lt;boolean&gt;
+     *     &lt;value&gt;and&lt;/value&gt;
+     *   &lt;/boolean&gt;
+     *   &lt;triple&gt;
+     *     &lt;boolean&gt;
+     *       &lt;value&gt;or&lt;/value&gt;
+     *     &lt;/boolean&gt;
+     *     &lt;searchClause&gt;
+     *       &lt;index&gt;au&lt;/index&gt;
+     *       &lt;relation&gt;
+     *         &lt;value&gt;=&lt;/value&gt;
+     *       &lt;/relation&gt;
+     *       &lt;term&gt;Kerninghan&lt;/term&gt;
+     *     &lt;/searchClause&gt;
+     *     &lt;searchClause&gt;
+     *       &lt;index&gt;au&lt;/index&gt;
+     *       &lt;relation&gt;
+     *         &lt;value&gt;=&lt;/value&gt;
+     *       &lt;/relation&gt;
+     *       &lt;term&gt;Ritchie&lt;/term&gt;
+     *     &lt;/searchClause&gt;
+     *   &lt;/triple&gt;
+     *   &lt;searchClause&gt;
+     *     &lt;index&gt;ti&lt;/index&gt;
+     *     &lt;relation&gt;
+     *       &lt;value&gt;=&lt;/value&gt;
+     *     &lt;/relation&gt;
+     *     &lt;term&gt;Unix&lt;/term&gt;
+     *   &lt;/searchClause&gt;
+     * &lt;/triple&gt;
+     * </PRE>
+     * <P>
+     * @param -c
+     * Causes the output to be written in CQL rather than XCQL - that
+     * is, a query equivalent to that which was input, is output.  In
+     * effect, the test harness acts as a query canonicaliser.
+     * @return
+     * The input query, either as XCQL [default] or CQL [if the
+     * <TT>-c</TT> option is supplied].
+     */
     public static void main (String[] args) {
-       if (args.length > 1) {
-           System.err.println("Usage: CQLParser [<CQL-query>]");
+       char mode = 'x';        // x=XCQL, c=CQL, p=PQF
+       String pfile = null;
+
+       Vector argv = new Vector();
+       for (int i = 0; i < args.length; i++) {
+           argv.add(args[i]);
+       }
+
+       if (argv.size() > 0 && argv.get(0).equals("-c")) {
+           mode = 'c';
+           argv.remove(0);
+       } else if (argv.size() > 1 && argv.get(0).equals("-p")) {
+           mode = 'p';
+           argv.remove(0);
+           pfile = (String) argv.get(0);
+           argv.remove(0);
+       }
+
+       if (argv.size() > 1) {
+           System.err.println(
+               "Usage: CQLParser [-c] [-p <pqf-properties> [<CQL-query>]");
            System.err.println("If unspecified, query is read from stdin");
            System.exit(1);
        }
 
        String cql;
-       if (args.length == 1) {
-           cql = args[0];
+       if (argv.size() == 1) {
+           cql = (String) argv.get(0);
        } else {
            byte[] bytes = new byte[10000];
            try {
@@ -263,11 +336,9 @@ public class CQLParser {
        }
 
        CQLParser parser = new CQLParser();
-       CQLNode root;
+       CQLNode root = null;
        try {
            root = parser.parse(cql);
-           debug("root='" + root + "'");
-           System.out.println(root.toCQL());
        } catch (CQLParseException ex) {
            System.err.println("Syntax error: " + ex.getMessage());
            System.exit(3);
@@ -275,5 +346,25 @@ public class CQLParser {
            System.err.println("Can't compile query: " + ex.getMessage());
            System.exit(4);
        }
+
+       try {
+           if (mode == 'c') {
+               System.out.println(root.toCQL());
+           } else if (mode == 'p') {
+               InputStream f = new FileInputStream(pfile);
+               if (f == null)
+                   throw new FileNotFoundException(pfile);
+
+               Properties config = new Properties();
+               config.load(f);
+               f.close();
+               System.out.println(root.toPQF(config));
+           } else {
+               System.out.print(root.toXCQL(0));
+           }
+       } catch (java.io.IOException ex) {
+           System.err.println("Can't render query: " + ex.getMessage());
+           System.exit(5);
+       }
     }
 }