From e52d49ef72a0d4531500e3e790fbe2e47fda8cba Mon Sep 17 00:00:00 2001 From: mike Date: Fri, 25 Oct 2002 07:38:16 +0000 Subject: [PATCH] Add initial version of most of the CQL*Node classes. Some little work towards fixing the parser. Add documentation directory and javadoc build commands. --- README | 14 ++- docs/README | 2 + src/org/z3950/zing/cql/CQLAndNode.java | 21 +++++ src/org/z3950/zing/cql/CQLNode.java | 31 +++++++ src/org/z3950/zing/cql/CQLNotNode.java | 21 +++++ src/org/z3950/zing/cql/CQLOrNode.java | 21 +++++ src/org/z3950/zing/cql/CQLParser.java | 155 +++++++++++-------------------- src/org/z3950/zing/cql/CQLTermNode.java | 54 +++++++++++ src/org/z3950/zing/cql/Makefile | 18 ++++ 9 files changed, 235 insertions(+), 102 deletions(-) create mode 100644 docs/README create mode 100644 src/org/z3950/zing/cql/CQLAndNode.java create mode 100644 src/org/z3950/zing/cql/CQLNode.java create mode 100644 src/org/z3950/zing/cql/CQLNotNode.java create mode 100644 src/org/z3950/zing/cql/CQLOrNode.java create mode 100644 src/org/z3950/zing/cql/CQLTermNode.java create mode 100644 src/org/z3950/zing/cql/Makefile diff --git a/README b/README index d0b3d9f..6d123c7 100644 --- 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 index 0000000..3b47757 --- /dev/null +++ b/docs/README @@ -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 index 0000000..170b76d --- /dev/null +++ b/src/org/z3950/zing/cql/CQLAndNode.java @@ -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 index 0000000..8811e94 --- /dev/null +++ b/src/org/z3950/zing/cql/CQLNode.java @@ -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 index 0000000..fa48591 --- /dev/null +++ b/src/org/z3950/zing/cql/CQLNotNode.java @@ -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 index 0000000..06b8b6d --- /dev/null +++ b/src/org/z3950/zing/cql/CQLOrNode.java @@ -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"; + } +} diff --git a/src/org/z3950/zing/cql/CQLParser.java b/src/org/z3950/zing/cql/CQLParser.java index bc19d48..0e68a27 100644 --- a/src/org/z3950/zing/cql/CQLParser.java +++ b/src/org/z3950/zing/cql/CQLParser.java @@ -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 http://zing.z3950.org/cql/index.html */ -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 "); + 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 index 0000000..b12883e --- /dev/null +++ b/src/org/z3950/zing/cql/CQLTermNode.java @@ -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) + "\n" + + indent(level+1) + "" + qualifier + "\n" + + indent(level+1) + "" + relation + "\n" + + indent(level+1) + "" + value + "\n" + + indent(level) + "\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 index 0000000..acd7ccc --- /dev/null +++ b/src/org/z3950/zing/cql/Makefile @@ -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/* -- 1.7.10.4