Add initial version of most of the CQL*Node classes.
authormike <mike>
Fri, 25 Oct 2002 07:38:16 +0000 (07:38 +0000)
committermike <mike>
Fri, 25 Oct 2002 07:38:16 +0000 (07:38 +0000)
Some little work towards fixing the parser.
Add documentation directory and javadoc build commands.

README
docs/README [new file with mode: 0644]
src/org/z3950/zing/cql/CQLAndNode.java [new file with mode: 0644]
src/org/z3950/zing/cql/CQLNode.java [new file with mode: 0644]
src/org/z3950/zing/cql/CQLNotNode.java [new file with mode: 0644]
src/org/z3950/zing/cql/CQLOrNode.java [new file with mode: 0644]
src/org/z3950/zing/cql/CQLParser.java
src/org/z3950/zing/cql/CQLTermNode.java [new file with mode: 0644]
src/org/z3950/zing/cql/Makefile [new file with mode: 0644]

diff --git a/README b/README
index d0b3d9f..6d123c7 100644 (file)
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-$Id: README,v 1.2 2002-10-24 16:06:34 mike Exp $
+$Id: README,v 1.3 2002-10-25 07:38:16 mike Exp $
 
 cql-java -- a free CQL compiler for Java
 
@@ -33,10 +33,18 @@ Library:
 
        import org.z3950.zing.cql.*
 
+       // Building a parse-tree by hand
+       CQLNode n1 = new CQLTermNode("dc.author", "=", "kernighan");
+       CQLNode n2 = new CQLTermNode("dc.title", "all", "elements style");
+       CQLNode root = new CQLAndNode(n1, n2);
+       System.out.println(root.toXCQL(3));
+
+       // Parsing a CQL query
        CQLParser parser = new CQLParser();
        CQLNode root = parser.parse("title=dinosaur");
-       print root.toXCQL();
-       print root.toPQF(qualSet);
+       System.out.println(root.toXCQL(0));
+       System.out.println(root.toCQL());
+       System.out.println(root.toPQF(qualSet));
        // ... where `qualSet' specifies CQL-qualfier => Z-attr mapping
 
 
diff --git a/docs/README b/docs/README
new file mode 100644 (file)
index 0000000..3b47757
--- /dev/null
@@ -0,0 +1,2 @@
+Automatically-generated documentation should appear here.
+cd ../src/org/z3950/zing/cql && make javadocs
diff --git a/src/org/z3950/zing/cql/CQLAndNode.java b/src/org/z3950/zing/cql/CQLAndNode.java
new file mode 100644 (file)
index 0000000..170b76d
--- /dev/null
@@ -0,0 +1,21 @@
+// $Id: CQLAndNode.java,v 1.1 2002-10-25 07:38:16 mike Exp $
+
+package org.z3950.zing.cql;
+
+
+/**
+ * Represents an AND node in a CQL parse-tree ...
+ * ###
+ *
+ * @version    $Id: CQLAndNode.java,v 1.1 2002-10-25 07:38:16 mike Exp $
+ */
+class CQLAndNode extends CQLBooleanNode {
+    public CQLAndNode(CQLNode left, CQLNode right) {
+       this.left = left;
+       this.right = right;
+    }
+
+    String op() {
+       return "and";
+    }
+}
diff --git a/src/org/z3950/zing/cql/CQLNode.java b/src/org/z3950/zing/cql/CQLNode.java
new file mode 100644 (file)
index 0000000..8811e94
--- /dev/null
@@ -0,0 +1,31 @@
+// $Id: CQLNode.java,v 1.1 2002-10-25 07:38:16 mike Exp $
+
+package org.z3950.zing.cql;
+
+
+/**
+ * Represents a node in a CQL parse-tree ...
+ * ###
+ *
+ * @version    $Id: CQLNode.java,v 1.1 2002-10-25 07:38:16 mike Exp $
+ */
+abstract class CQLNode {
+    abstract String toXCQL(int level);
+    abstract String toCQL();
+
+    protected String indent(int level) {
+       String x = "";
+       while (level-- > 0) {
+           x += "  ";
+       }
+       return x;
+    }
+
+    // Test harness
+    public static void main (String[] args) {
+       CQLNode n1 = new CQLTermNode("dc.author", "=", "kernighan");
+       CQLNode n2 = new CQLTermNode("dc.title", "all", "elements style");
+       CQLNode root = new CQLAndNode(n1, n2);
+       System.out.println(root.toXCQL(3));
+    }
+}
diff --git a/src/org/z3950/zing/cql/CQLNotNode.java b/src/org/z3950/zing/cql/CQLNotNode.java
new file mode 100644 (file)
index 0000000..fa48591
--- /dev/null
@@ -0,0 +1,21 @@
+// $Id: CQLNotNode.java,v 1.1 2002-10-25 07:38:16 mike Exp $
+
+package org.z3950.zing.cql;
+
+
+/**
+ * Represents a NOT node in a CQL parse-tree ...
+ * ###
+ *
+ * @version    $Id: CQLNotNode.java,v 1.1 2002-10-25 07:38:16 mike Exp $
+ */
+class CQLNotNode extends CQLBooleanNode {
+    public CQLNotNode(CQLNode left, CQLNode right) {
+       this.left = left;
+       this.right = right;
+    }
+
+    String op() {
+       return "not";
+    }
+}
diff --git a/src/org/z3950/zing/cql/CQLOrNode.java b/src/org/z3950/zing/cql/CQLOrNode.java
new file mode 100644 (file)
index 0000000..06b8b6d
--- /dev/null
@@ -0,0 +1,21 @@
+// $Id: CQLOrNode.java,v 1.1 2002-10-25 07:38:16 mike Exp $
+
+package org.z3950.zing.cql;
+
+
+/**
+ * Represents an OR node in a CQL parse-tree ...
+ * ###
+ *
+ * @version    $Id: CQLOrNode.java,v 1.1 2002-10-25 07:38:16 mike Exp $
+ */
+class CQLOrNode extends CQLBooleanNode {
+    public CQLOrNode(CQLNode left, CQLNode right) {
+       this.left = left;
+       this.right = right;
+    }
+
+    String op() {
+       return "or";
+    }
+}
index bc19d48..0e68a27 100644 (file)
@@ -1,4 +1,4 @@
-// $Id: CQLParser.java,v 1.3 2002-10-24 16:06:34 mike Exp $
+// $Id: CQLParser.java,v 1.4 2002-10-25 07:38:16 mike Exp $
 
 package org.z3950.zing.cql;
 import java.util.Properties;
@@ -13,42 +13,27 @@ import java.io.StreamTokenizer;
  * Compiles a CQL string into a parse tree ...
  * ###
  *
- * @version    $Id: CQLParser.java,v 1.3 2002-10-24 16:06:34 mike Exp $
+ * @version    $Id: CQLParser.java,v 1.4 2002-10-25 07:38:16 mike Exp $
  * @see                <A href="http://zing.z3950.org/cql/index.html"
  *                     >http://zing.z3950.org/cql/index.html</A>
  */
-class CQLCompiler {
+class CQLParser {
     private String cql;
-    private String qualset;
-    private Properties qualsetProperties;
     private StreamTokenizer st;
 
     private class CQLParseException extends Exception {
        CQLParseException(String s) { super(s); }
     }
 
-    public CQLCompiler(String cql, String qualset) {
-       this.cql = cql;
-       this.qualset = qualset;
+    public CQLParser() {
+       // Nothing to do: do we need this constructor, then?
     }
 
-    public String convertToPQN()
+    public CQLNode parse(String cql)
        throws FileNotFoundException, IOException {
-
-       if (qualsetProperties == null) {
-           //      ### Could think about caching named qualifier sets
-           //          across compilations (i.e. shared, in a static
-           //          Hashtable, between multiple CQLCompiler
-           //          instances.)  Probably not worth it.
-           InputStream is = this.getClass().getResourceAsStream(qualset);
-           if (is == null)
-               throw new FileNotFoundException("getResourceAsStream(" +
-                                               qualset + ")");
-           qualsetProperties = new Properties();
-           qualsetProperties.load(is);
-       }
-
+       this.cql = cql;
        st = new StreamTokenizer(new StringReader(cql));
+       // ### these settings are wrong
        st.wordChars('/', '/');
        st.wordChars('0', '9'); // ### but 1 is still recognised as TT_NUM
        st.wordChars('.', '.');
@@ -66,9 +51,9 @@ class CQLCompiler {
 //     }
 
        st.nextToken();
-       String ret;
+       CQLNode root;
        try {
-           ret = parse_expression();
+           root = parse_expression();
        } catch (CQLParseException ex) {
            System.err.println("### Oops: " + ex);
            return null;
@@ -79,70 +64,44 @@ class CQLCompiler {
            return null;
        }
 
-       // Interpret attributes as BIB-1 unless otherwise specified
-       return "@attrset bib-1 " + ret;
+       return root;
     }
 
-    private String parse_expression()
+    private CQLNode parse_expression()
        throws CQLParseException, IOException {
-       String term = parse_term();
+       CQLNode term = parse_term();
 
        while (st.ttype == st.TT_WORD) {
            String op = st.sval.toLowerCase();
-           if (!st.sval.equals("and") &&
-               !st.sval.equals("or") &&
-               !st.sval.equals("not"))
-               break;
-           match(st.TT_WORD);
-           String term2 = parse_term();
-           term = "@" + op + " " + term + " " + term2;
+           if (st.sval.equals("and")) {
+               match(st.TT_WORD);
+               CQLNode term2 = parse_term();
+               term = new CQLAndNode(term, term2);
+           } else if (st.sval.equals("or")) {
+               match(st.TT_WORD);
+               CQLNode term2 = parse_term();
+               term = new CQLOrNode(term, term2);
+           } else if (st.sval.equals("not")) {
+               match(st.TT_WORD);
+               CQLNode term2 = parse_term();
+               term = new CQLNotNode(term, term2);
+           }
        }
 
        return term;
     }
 
-    private String parse_term()
+    private CQLNode parse_term()
        throws CQLParseException, IOException {
        if (st.ttype == '(') {
            match('(');
-           String expr = parse_expression();
+           CQLNode expr = parse_expression();
            match(')');
            return expr;
        }
 
-       String word = null;
-       String attrs = "";
-
-       // ### We treat ',' and '=' equivalently here, which isn't quite right.
-       while (st.ttype == st.TT_WORD) {
-           word = st.sval;
-           match(st.TT_WORD);
-           if (st.ttype != '=' && st.ttype != ',') {
-               // end of qualifer list
-               break;
-           }
-
-           String attr = qualsetProperties.getProperty(word);
-           if (attr == null) {
-               throw new CQLParseException("unrecognised qualifier: " + word);
-           }
-           attrs = attrs + attr + " ";
-           match(st.ttype);
-           word = null;        // mark as not-yet-read
-       }
-
-       if (word == null) {
-           // got to the end of a "foo,bar=" sequence
-           word = st.sval;
-           if (st.ttype != '\'' || st.ttype != '"') {
-               word = "\"" + word + "\"";
-               match(st.ttype);
-           } else {
-               match(st.TT_WORD);
-           }
-       }
-
-       return attrs + word;
+       String word = st.sval;
+       return new CQLTermNode("x", "=", word);
     }
 
     private void match(int token)
@@ -161,47 +120,45 @@ class CQLCompiler {
     private static String render(StreamTokenizer st, int token, String str) {
        String ret;
 
-       switch (token) {
-       case st.TT_EOF: return "EOF";
-       case st.TT_EOL: return "EOL";
-       case st.TT_NUMBER: return "number";
-       case st.TT_WORD: ret = "word"; break;
-       case '"': case '\'': ret = "string"; break;
-       default: return "'" + String.valueOf((char) token) + "'";
+       if (token == st.TT_EOF) {
+           return "EOF";
+       } else if (token == st.TT_EOL) {
+           return "EOL";
+       } else if (token == st.TT_NUMBER) {
+           return "number";
+       } else if (token == st.TT_WORD) {
+           return "word";
+       } else if (token == '"' && token == '\'') {
+           return "string";
        }
 
-       if (str != null)
-           ret += "(\"" + str + "\")";
-       return ret;
+        return "'" + String.valueOf((char) token) + "'";
     }
 
-    // ### Not really the right place for this test harness.
+
+    // Test harness.
     //
-    // e.g. java uk.org.miketaylor.zoom.CQLCompiler
-    //         '(au=Kerninghan or au=Ritchie) and ti=Unix' qualset.properties
+    // e.g. echo '(au=Kerninghan or au=Ritchie) and ti=Unix' |
+    //                         java org.z3950.zing.cql.CQLParser
     // yields:
-    // @and
-    //         @or
-    //                 @attr 1=1 @attr 4=1 Kerninghan
-    //                 @attr 1=1 @attr 4=1 Ritchie
-    //         @attr 1=4 @attr 4=1 Unix
+    // ###
     //
     public static void main (String[] args) {
-       if (args.length != 2) {
-           System.err.println("Usage: CQLQuery <cql> <qualset>");
+       if (args.length != 0) {
+           System.err.println("Usage: " + args[0]);
            System.exit(1);
        }
 
-       CQLCompiler cc = new CQLCompiler(args[0], args[1]);
+       byte[] bytes = new byte[1000];
        try {
-           String pqn = cc.convertToPQN();
-           System.out.println(pqn);
-       } catch (FileNotFoundException ex) {
-           System.err.println("Can't find qualifier set: " + ex);
-           System.exit(2);
-       } catch (IOException ex) {
-           System.err.println("Can't read qualifier set: " + ex);
+           int nbytes = System.in.read(bytes);
+       } catch (java.io.IOException ex) {
+           System.err.println("Can't read query: " + ex);
            System.exit(2);
        }
+       String cql = String(bytes);
+       CQLParser parser = new CQLParser();
+       CQLNode root = parser.parse(cql);
+       System.out.println(root.toXCQL());
     }
 }
diff --git a/src/org/z3950/zing/cql/CQLTermNode.java b/src/org/z3950/zing/cql/CQLTermNode.java
new file mode 100644 (file)
index 0000000..b12883e
--- /dev/null
@@ -0,0 +1,54 @@
+// $Id: CQLTermNode.java,v 1.1 2002-10-25 07:38:17 mike Exp $
+
+package org.z3950.zing.cql;
+
+
+/**
+ * Represents a terminal node in a CQL parse-tree ...
+ * ###
+ *
+ * @version    $Id: CQLTermNode.java,v 1.1 2002-10-25 07:38:17 mike Exp $
+ */
+class CQLTermNode extends CQLNode {
+    private String qualifier;
+    private String relation;
+    private String value;
+
+    public CQLTermNode(String qualifier, String relation, String value) {
+       this.qualifier = qualifier;
+       this.relation = relation;
+       this.value = value;
+    }
+
+    String toXCQL(int level) {
+       return (indent(level) + "<searchClause>\n" +
+               indent(level+1) + "<index>" + qualifier + "<index>\n" +
+               indent(level+1) + "<relation>" + relation + "<relation>\n" +
+               indent(level+1) + "<term>" + value + "<term>\n" +
+               indent(level) + "</searchClause>\n");
+    }
+
+    String toCQL() {
+       String res = value;
+
+       if (res.indexOf('"') != -1) {
+           // ### precede each '"' with a '/'
+       }
+
+       if (res.indexOf('"') != -1 ||
+           res.indexOf(' ') != -1 ||
+           res.indexOf('\t') != -1 ||
+           res.indexOf('=') != -1 ||
+           res.indexOf('<') != -1 ||
+           res.indexOf('>') != -1 ||
+           res.indexOf('/') != -1 ||
+           res.indexOf('(') != -1 ||
+           res.indexOf(')') != -1) {
+           res = '"' + res + '"';
+       }
+
+       // ### The qualifier may need quoting.
+       // ### We don't always need spaces around `relation'.
+       return qualifier + " " + relation + " " + value;
+    }
+}
diff --git a/src/org/z3950/zing/cql/Makefile b/src/org/z3950/zing/cql/Makefile
new file mode 100644 (file)
index 0000000..acd7ccc
--- /dev/null
@@ -0,0 +1,18 @@
+# $Id: Makefile,v 1.1 2002-10-25 07:38:17 mike Exp $
+
+all: CQLNode.class CQLTermNode.class CQLBooleanNode.class \
+       CQLAndNode.class CQLOrNode.class CQLNotNode.class \
+       CQLParser.class
+
+javadocs:
+       nice javadoc -d ../../../../../docs -author -version \
+               -windowtitle cql-java org.z3950.zing.cql
+
+%.class: %.java
+       javac $<
+
+clean:
+       rm -f *.class
+
+cleandocs:
+       rm -r docs/*