--- /dev/null
+target/
+*.swp
+*~
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.z3950.zing</groupId>
+ <artifactId>cql-java</artifactId>
+ <packaging>jar</packaging>
+ <version>1.3</version>
+ <name>cql-java</name>
+ <url>http://maven.apache.org</url>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.0.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
+
--- /dev/null
+// $Id: CQLAndNode.java,v 1.9 2007-06-29 12:48:21 mike Exp $
+
+package org.z3950.zing.cql;
+
+
+/**
+ * Represents an AND node in a CQL parse-tree.
+ *
+ * @version $Id: CQLAndNode.java,v 1.9 2007-06-29 12:48:21 mike Exp $
+ */
+public class CQLAndNode extends CQLBooleanNode {
+ /**
+ * Creates a new AND node with the specified left- and right-hand
+ * sides and modifiers.
+ */
+ public CQLAndNode(CQLNode left, CQLNode right, ModifierSet ms) {
+ super(left, right, ms);
+ }
+
+ // ### Too much code duplication here with OR and NOT
+ byte[] opType1() {
+ byte[] op = new byte[5];
+ putTag(CONTEXT, 46, CONSTRUCTED, op, 0); // Operator
+ putLen(2, op, 2);
+ putTag(CONTEXT, 0, PRIMITIVE, op, 3); // and
+ putLen(0, op, 4);
+ return op;
+ }
+}
--- /dev/null
+// $Id: CQLBooleanNode.java,v 1.18 2007-07-03 16:03:00 mike Exp $
+
+package org.z3950.zing.cql;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ * Represents a boolean node in a CQL parse-tree.
+ *
+ * @version $Id: CQLBooleanNode.java,v 1.18 2007-07-03 16:03:00 mike Exp $
+ */
+public abstract class CQLBooleanNode extends CQLNode {
+ /**
+ * The root of a parse-tree representing the left-hand side.
+ */
+ public CQLNode left;
+
+ /**
+ * The root of a parse-tree representing the right-hand side.
+ */
+ public CQLNode right;
+
+ /**
+ * The set of modifiers that are applied to this boolean.
+ */
+ public ModifierSet ms;
+
+ protected CQLBooleanNode(CQLNode left, CQLNode right, ModifierSet ms) {
+ this.left = left;
+ this.right = right;
+ this.ms = ms;
+ }
+
+ public String toXCQL(int level, Vector<CQLPrefix> prefixes,
+ Vector<ModifierSet> sortkeys) {
+ return (indent(level) + "<triple>\n" +
+ renderPrefixes(level+1, prefixes) +
+ ms.toXCQL(level+1, "boolean") +
+ indent(level+1) + "<leftOperand>\n" +
+ left.toXCQL(level+2) +
+ indent(level+1) + "</leftOperand>\n" +
+ indent(level+1) + "<rightOperand>\n" +
+ right.toXCQL(level+2) +
+ indent(level+1) + "</rightOperand>\n" +
+ renderSortKeys(level+1, sortkeys) +
+ indent(level) + "</triple>\n");
+ }
+
+ public String toCQL() {
+ // ### We don't always need parens around the operands
+ return ("(" + left.toCQL() + ")" +
+ " " + ms.toCQL() + " " +
+ "(" + right.toCQL() + ")");
+ }
+
+ public String toPQF(Properties config) throws PQFTranslationException {
+ return ("@" + opPQF() +
+ " " + left.toPQF(config) +
+ " " + right.toPQF(config));
+ }
+
+ // represents the operation for PQF: overridden for CQLProxNode
+ String opPQF() { return ms.getBase(); }
+
+ public byte[] toType1BER(Properties config) throws PQFTranslationException {
+ System.out.println("in CQLBooleanNode.toType1BER(): PQF=" +
+ toPQF(config));
+ byte[] rpn1 = left.toType1BER(config);
+ byte[] rpn2 = right.toType1BER(config);
+ byte[] op = opType1();
+ byte[] rpnStructure = new byte[rpn1.length+rpn2.length+op.length+4];
+
+ // rpnRpnOp
+ int offset = putTag(CONTEXT, 1, CONSTRUCTED, rpnStructure, 0);
+
+ rpnStructure[offset++] = (byte)(0x80&0xff); // indefinite length
+ System.arraycopy(rpn1, 0, rpnStructure, offset, rpn1.length);
+ offset += rpn1.length;
+ System.arraycopy(rpn2, 0, rpnStructure, offset, rpn2.length);
+ offset += rpn2.length;
+ System.arraycopy(op, 0, rpnStructure, offset, op.length);
+ offset += op.length;
+ rpnStructure[offset++] = 0x00; // end rpnRpnOp
+ rpnStructure[offset++] = 0x00;
+ return rpnStructure;
+ }
+
+ abstract byte[] opType1();
+}
--- /dev/null
+// $Id: CQLGenerator.java,v 1.9 2007-07-03 15:41:35 mike Exp $
+
+package org.z3950.zing.cql;
+import java.util.Properties;
+import java.util.Random;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+
+
+/**
+ * A generator that produces random CQL queries.
+ * <P>
+ * Why is that useful? Mainly to produce test-cases for CQL parsers
+ * (including the <TT>CQLParser</TT> class in this package): you can
+ * generate a random search tree, render it to XCQL and remember the
+ * result. Then decompile the tree to CQL, feed the generated CQL to
+ * the parser of your choice, and check that the XCQL it comes up with
+ * is the same what you got from your initial rendering.
+ * <P>
+ * This code is based on the same grammar as the <TT>CQLParser</TT> class in
+ * this distribution - there is a <TT>generate_<I>x</I>()</TT> method
+ * for each grammar element <I>X</I>.
+ *
+ * @version $Id: CQLGenerator.java,v 1.9 2007-07-03 15:41:35 mike Exp $
+ * @see <A href="http://zing.z3950.org/cql/index.html"
+ * >http://zing.z3950.org/cql/index.html</A>
+ */
+public class CQLGenerator {
+ Properties params;
+ Random rnd;
+ static private boolean DEBUG = false;
+
+ /**
+ * Creates a new CQL generator with the specified parameters.
+ * <P>
+ * @param params
+ * A <TT>Properties</TT> table containing configuration
+ * parameters for the queries to be generated by this generator.
+ * Recognised parameters are:
+ * <P>
+ * <DL>
+ * <DT><TT>seed</TT></DT>
+ * <DD>
+ * If specified, this is a <TT>long</TT> used to seed the
+ * random number generator, so that the CQL generator can be
+ * run repeatably, giving the same results each time. If it's
+ * omitted, then no seed is explicitly specified, and the
+ * results of each run will be different (so long as you don't
+ * run it more that 2^32 times :-)
+ * <P>
+ * </DD>
+ * <DT><TT>complexQuery</TT></DT>
+ * <DD>
+ * [mandatory] A floating-point number between 0.0 and 1.0,
+ * indicating the probability for each <TT>cql-query</TT> node
+ * that it will be expanded into a ``complex query''
+ * (<TT>cql-query boolean search-clause</TT>) rather
+ * than a <TT>search-clause</TT>.
+ * <P>
+ * </DD>
+ * <DT><TT>complexClause</TT></DT>
+ * <DD>
+ * [mandatory] A floating-point number between 0.0 and 1.0,
+ * indicating the probability for each <TT>search-clause</TT>
+ * node that it will be expanded into a full sub-query rather
+ * than an <TT>[ index relation ] term</TT> triplet.
+ * <P>
+ * </DD>
+ * <DT><TT>proxOp</TT></DT>
+ * <DD>
+ * [mandatory] A floating-point number between 0.0 and 1.0,
+ * indicating the probability that each boolean operator will
+ * be chosen to be proximity operation; otherwise, the three
+ * simpler boolean operations (<TT>and</TT>, <TT>or</TT> and
+ * <TT>not</TT>) are chosen with equal probability.
+ * <P>
+ * </DD>
+ * <DT><TT>equalsRelation</TT></DT>
+ * <DD>
+ * [mandatory] A floating-point number between 0.0 and 1.0,
+ * indicating the probability that each relation will be chosen
+ * to be <TT>=</TT> - this is treated as a special case, since
+ * it's likely to be by far the most common relation in
+ * ``real life'' searches.
+ * <P>
+ * </DD>
+ * <DT><TT>numericRelation</TT></DT>
+ * <DD>
+ * [mandatory] A floating-point number between 0.0 and 1.0,
+ * indicating the probability that a relation, having chosen
+ * not to be <TT>=</TT>, is instead chosen to be one of the six
+ * numeric relations (<TT><</TT>, <TT>></TT>,
+ * <TT><=</TT>, <TT>>=</TT>, <TT><></TT> and
+ * <TT>=</TT>).
+ * <P>
+ * </DD>
+ * </DL>
+ */
+ public CQLGenerator(Properties params) {
+ this.params = params;
+ String seed = params.getProperty("seed");
+ if (seed != null)
+ rnd = new Random(new Long(seed).longValue());
+ else
+ rnd = new Random();
+ }
+
+ private static void debug(String str) {
+ if (DEBUG)
+ System.err.println("DEBUG: " + str);
+ }
+
+ /**
+ * Generates a single random CQL query.
+ * <P>
+ * Uses the parameters that were associated with the generator
+ * when it was created. You are free to create as many random
+ * queries as you wish from a single generator; each of them will
+ * use the same parameters.
+ * <P>
+ * @return
+ * A <TT>CQLNode</TT> that is the root of the generated tree.
+ * That tree may be rendered in XCQL using its <TT>toXCQL()</TT>
+ * method, or decompiled into CQL using its <TT>toCQL</TT>
+ * method.
+ */
+ public CQLNode generate() throws MissingParameterException {
+ return generate_cql_query();
+ }
+
+ private CQLNode generate_cql_query() throws MissingParameterException {
+ if (!maybe("complexQuery")) {
+ return generate_search_clause();
+ }
+
+ CQLNode node1 = generate_cql_query();
+ CQLNode node2 = generate_search_clause();
+ // ### should generate prefix-mapping nodes
+ if (maybe("proxOp")) {
+ // ### generate proximity nodes
+ } else {
+ switch (rnd.nextInt(3)) {
+ case 0: return new CQLAndNode(node1, node2, new ModifierSet("and"));
+ case 1: return new CQLOrNode (node1, node2, new ModifierSet("or"));
+ case 2: return new CQLNotNode(node1, node2, new ModifierSet("not"));
+ }
+ }
+
+ return generate_search_clause();
+ }
+
+ private CQLNode generate_search_clause() throws MissingParameterException {
+ if (maybe("complexClause")) {
+ return generate_cql_query();
+ }
+
+ // ### Should sometimes generate index/relation-free terms
+ String index = generate_index();
+ CQLRelation relation = generate_relation();
+ String term = generate_term();
+
+ return new CQLTermNode(index, relation, term);
+ }
+
+ // ### Should probably be more configurable
+ private String generate_index() {
+ String index = ""; // shut up compiler warning
+ if (rnd.nextInt(2) == 0) {
+ switch (rnd.nextInt(3)) {
+ case 0: index = "dc.author"; break;
+ case 1: index = "dc.title"; break;
+ case 2: index = "dc.subject"; break;
+ }
+ } else {
+ switch (rnd.nextInt(4)) {
+ case 0: index = "bath.author"; break;
+ case 1: index = "bath.title"; break;
+ case 2: index = "bath.subject"; break;
+ case 3: index = "foo>bar"; break;
+ }
+ }
+
+ return index;
+ }
+
+ private CQLRelation generate_relation() throws MissingParameterException {
+ String base = generate_base_relation();
+ CQLRelation rel = new CQLRelation(base);
+ // ### should generate modifiers too
+ return rel;
+ }
+
+ private String generate_base_relation() throws MissingParameterException {
+ if (maybe("equalsRelation")) {
+ return "=";
+ } else if (maybe("numericRelation")) {
+ return generate_numeric_relation();
+ } else {
+ switch (rnd.nextInt(3)) {
+ case 0: return "exact";
+ case 1: return "all";
+ case 2: return "any";
+ }
+ }
+
+ // NOTREACHED
+ return ""; // shut up compiler warning
+ }
+
+ // ### could read candidate terms from /usr/dict/words
+ // ### should introduce wildcard characters
+ // ### should generate multi-word terms
+ private String generate_term() {
+ switch (rnd.nextInt(10)) {
+ case 0: return "cat";
+ case 1: return "\"cat\"";
+ case 2: return "comp.os.linux";
+ case 3: return "xml:element";
+ case 4: return "<xml.element>";
+ case 5: return "prox/word/>=/5";
+ case 6: return "";
+ case 7: return "frog fish";
+ case 8: return "the complete dinosaur";
+ case 9: return "foo*bar";
+ }
+
+ // NOTREACHED
+ return ""; // shut up compiler warning
+ }
+
+ private String generate_numeric_relation() {
+ switch (rnd.nextInt(6)) {
+ case 0: return "<";
+ case 1: return ">";
+ case 2: return "<=";
+ case 3: return ">=";
+ case 4: return "<>";
+ case 5: return "=";
+ }
+
+ // NOTREACHED
+ return ""; // shut up compiler warning
+ }
+
+ boolean maybe(String param) throws MissingParameterException {
+ String probability = params.getProperty(param);
+ if (probability == null)
+ throw new MissingParameterException(param);
+
+ double dice = rnd.nextDouble();
+ double threshhold = new Double(probability).doubleValue();
+ boolean res = dice < threshhold;
+ debug("dice=" + String.valueOf(dice).substring(0, 8) +
+ " vs. " + threshhold + "='" + param + "': " + res);
+ return res;
+ }
+
+
+ /**
+ * A simple test-harness for the generator.
+ * <P>
+ * It generates a single random query using the parameters
+ * specified in a nominated properties file, plus any additional
+ * <I>name value</I> pairs provided on the command-line, and
+ * decompiles it into CQL which is written to standard output.
+ * <P>
+ * For example,
+ * <TT>java org.z3950.zing.cql.CQLGenerator
+ * etc/generate.properties seed 18398</TT>,
+ * where the file <TT>generate.properties</TT> contains:<PRE>
+ * complexQuery=0.4
+ * complexClause=0.4
+ * equalsRelation=0.5
+ * numericRelation=0.7
+ * proxOp=0.0
+ * </PRE>
+ * yields:<PRE>
+ * ((dc.author = "<xml.element>") or (bath.title = cat)) and
+ * (dc.subject >= "the complete dinosaur")
+ * </PRE>
+ * <P>
+ * @param configFile
+ * The name of a properties file from which to read the
+ * configuration parameters (see above).
+ * @param name
+ * The name of a configuration parameter.
+ * @param value
+ * The value to assign to the configuration parameter named in
+ * the immediately preceding command-line argument.
+ * @return
+ * A CQL query expressed in a form that should be comprehensible
+ * to all conformant CQL compilers.
+ */
+ public static void main (String[] args) throws Exception {
+ if (args.length % 2 != 1) {
+ System.err.println("Usage: CQLGenerator <props-file> "+
+ "[<name> <value>]...");
+ System.exit(1);
+ }
+
+ String configFile = args[0];
+ InputStream f = new FileInputStream(configFile);
+ if (f == null)
+ throw new FileNotFoundException(configFile);
+
+ Properties params = new Properties();
+ params.load(f);
+ f.close();
+ for (int i = 1; i < args.length; i += 2)
+ params.setProperty(args[i], args[i+1]);
+
+ CQLGenerator generator = new CQLGenerator(params);
+ CQLNode tree = generator.generate();
+ System.out.println(tree.toCQL());
+ }
+}
--- /dev/null
+// $Id: CQLLexer.java,v 1.14 2007-07-03 13:30:42 mike Exp $
+
+package org.z3950.zing.cql;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.util.Hashtable;
+
+
+// This is a semi-trivial subclass for java.io.StreamTokenizer that:
+// * Has a halfDecentPushBack() method that actually works
+// * Includes a render() method
+// * Knows about the multi-character tokens "<=", ">=" and "<>"
+// * Recognises a set of keywords as tokens in their own right
+// * Includes some primitive debugging-output facilities
+// It's used only by CQLParser.
+//
+class CQLLexer extends StreamTokenizer {
+ // New publicly visible token-types
+ static int TT_LE = 1000; // The "<=" relation
+ static int TT_GE = 1001; // The ">=" relation
+ static int TT_NE = 1002; // The "<>" relation
+ static int TT_EQEQ = 1003; // The "==" relation
+ static int TT_AND = 1004; // The "and" boolean
+ static int TT_OR = 1005; // The "or" boolean
+ static int TT_NOT = 1006; // The "not" boolean
+ static int TT_PROX = 1007; // The "prox" boolean
+ static int TT_SORTBY = 1008; // The "sortby" operator
+
+ // Support for keywords. It would be nice to compile this linear
+ // list into a Hashtable, but it's hard to store ints as hash
+ // values, and next to impossible to use them as hash keys. So
+ // we'll just scan the (very short) list every time we need to do
+ // a lookup.
+ private class Keyword {
+ int token;
+ String keyword;
+ Keyword(int token, String keyword) {
+ this.token = token;
+ this.keyword = keyword;
+ }
+ }
+ // This should logically be static, but Java won't allow it :-P
+ private Keyword[] keywords = {
+ new Keyword(TT_AND, "and"),
+ new Keyword(TT_OR, "or"),
+ new Keyword(TT_NOT, "not"),
+ new Keyword(TT_PROX, "prox"),
+ new Keyword(TT_SORTBY, "sortby"),
+ };
+
+ // For halfDecentPushBack() and the code at the top of nextToken()
+ private static int TT_UNDEFINED = -1000;
+ private int saved_ttype = TT_UNDEFINED;
+ private double saved_nval;
+ private String saved_sval;
+
+ // Controls debugging output
+ private static boolean DEBUG;
+
+ CQLLexer(String cql, boolean lexdebug) {
+ super(new StringReader(cql));
+ wordChars('!', '?'); // ASCII-dependency!
+ wordChars('[', '`'); // ASCII-dependency!
+ quoteChar('"');
+ ordinaryChar('=');
+ ordinaryChar('<');
+ ordinaryChar('>');
+ ordinaryChar('/');
+ ordinaryChar('(');
+ ordinaryChar(')');
+ wordChars('\'', '\''); // prevent this from introducing strings
+ parseNumbers();
+ DEBUG = lexdebug;
+ }
+
+ private static void debug(String str) {
+ if (DEBUG)
+ System.err.println("LEXDEBUG: " + str);
+ }
+
+ // I don't honestly understand why we need this, but the
+ // documentation for java.io.StreamTokenizer.pushBack() is pretty
+ // vague about its semantics, and it seems to me that they could
+ // be summed up as "it doesn't work". This version has the very
+ // clear semantics "pretend I didn't call nextToken() just then".
+ //
+ private void halfDecentPushBack() {
+ saved_ttype = ttype;
+ saved_nval = nval;
+ saved_sval = sval;
+ }
+
+ public int nextToken() throws java.io.IOException {
+ if (saved_ttype != TT_UNDEFINED) {
+ ttype = saved_ttype;
+ nval = saved_nval;
+ sval = saved_sval;
+ saved_ttype = TT_UNDEFINED;
+ debug("using saved ttype=" + ttype + ", " +
+ "nval=" + nval + ", sval='" + sval + "'");
+ return ttype;
+ }
+
+ underlyingNextToken();
+ if (ttype == '<') {
+ debug("token starts with '<' ...");
+ underlyingNextToken();
+ if (ttype == '=') {
+ debug("token continues with '=' - it's '<='");
+ ttype = TT_LE;
+ } else if (ttype == '>') {
+ debug("token continues with '>' - it's '<>'");
+ ttype = TT_NE;
+ } else {
+ debug("next token is " + render() + " (pushed back)");
+ halfDecentPushBack();
+ ttype = '<';
+ debug("AFTER: ttype is now " + ttype + " - " + render());
+ }
+ } else if (ttype == '>') {
+ debug("token starts with '>' ...");
+ underlyingNextToken();
+ if (ttype == '=') {
+ debug("token continues with '=' - it's '>='");
+ ttype = TT_GE;
+ } else {
+ debug("next token is " + render() + " (pushed back)");
+ halfDecentPushBack();
+ ttype = '>';
+ debug("AFTER: ttype is now " + ttype + " - " + render());
+ }
+ } else if (ttype == '=') {
+ debug("token starts with '=' ...");
+ underlyingNextToken();
+ if (ttype == '=') {
+ debug("token continues with '=' - it's '=='");
+ ttype = TT_EQEQ;
+ } else {
+ debug("next token is " + render() + " (pushed back)");
+ halfDecentPushBack();
+ ttype = '=';
+ debug("AFTER: ttype is now " + ttype + " - " + render());
+ }
+ }
+
+ debug("done nextToken(): ttype=" + ttype + ", " +
+ "nval=" + nval + ", " + "sval='" + sval + "'" +
+ " (" + render() + ")");
+
+ return ttype;
+ }
+
+ // It's important to do keyword recognition here at the lowest
+ // level, otherwise when one of these words follows "<" or ">"
+ // (which can be the beginning of multi-character tokens) it gets
+ // pushed back as a string, and its keywordiness is not
+ // recognised.
+ //
+ public int underlyingNextToken() throws java.io.IOException {
+ super.nextToken();
+ if (ttype == TT_WORD)
+ for (int i = 0; i < keywords.length; i++)
+ if (sval.equalsIgnoreCase(keywords[i].keyword))
+ ttype = keywords[i].token;
+
+ return ttype;
+ }
+
+ // Simpler interface for the usual case: current token with quoting
+ String render() {
+ return render(ttype, true);
+ }
+
+ String render(int token, boolean quoteChars) {
+ if (token == TT_EOF) {
+ return "EOF";
+ } else if (token == TT_NUMBER) {
+ if ((double) nval == (int) nval) {
+ return new Integer((int) nval).toString();
+ } else {
+ return new Double((double) nval).toString();
+ }
+ } else if (token == TT_WORD) {
+ return "word: " + sval;
+ } else if (token == '"') {
+ return "string: \"" + sval + "\"";
+ } else if (token == TT_LE) {
+ return "<=";
+ } else if (token == TT_GE) {
+ return ">=";
+ } else if (token == TT_NE) {
+ return "<>";
+ } else if (token == TT_EQEQ) {
+ return "==";
+ }
+
+ // Check whether its associated with one of the keywords
+ for (int i = 0; i < keywords.length; i++)
+ if (token == keywords[i].token)
+ return keywords[i].keyword;
+
+ // Otherwise it must be a single character, such as '(' or '/'.
+ String res = String.valueOf((char) token);
+ if (quoteChars) res = "'" + res + "'";
+ return res;
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length > 1) {
+ System.err.println("Usage: CQLLexer [<CQL-query>]");
+ System.err.println("If unspecified, query is read from stdin");
+ System.exit(1);
+ }
+
+ String cql;
+ if (args.length == 1) {
+ cql = args[0];
+ } else {
+ byte[] bytes = new byte[10000];
+ try {
+ // Read in the whole of standard input in one go
+ int nbytes = System.in.read(bytes);
+ } catch (java.io.IOException ex) {
+ System.err.println("Can't read query: " + ex.getMessage());
+ System.exit(2);
+ }
+ cql = new String(bytes);
+ }
+
+ CQLLexer lexer = new CQLLexer(cql, true);
+ int token;
+ while ((token = lexer.nextToken()) != TT_EOF) {
+ // Nothing to do: debug() statements render tokens for us
+ }
+ }
+}
--- /dev/null
+// $Id: CQLNode.java,v 1.26 2007-07-03 13:36:03 mike Exp $
+
+package org.z3950.zing.cql;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ * Represents a node in a CQL parse-tree.
+ *
+ * @version $Id: CQLNode.java,v 1.26 2007-07-03 13:36:03 mike Exp $
+ */
+public abstract class CQLNode {
+ CQLNode() {} // prevent javadoc from documenting this
+
+ /**
+ * Returns the name of the result-set to which this query is a
+ * reference, if and only if the entire query consists only of a
+ * result-set reference. If it's anything else, including a
+ * boolean combination of a result-set reference with something
+ * else, then null is returned instead.
+ * @return the name of the referenced result-set
+ */
+ public String getResultSetName() {
+ return null;
+ }
+
+ /**
+ * Translates a parse-tree into an XCQL document.
+ * <P>
+ * @param level
+ * The number of levels to indent the top element of the XCQL
+ * document. This will typically be 0 when invoked by an
+ * application; it takes higher values when this method is
+ * invoked recursively for nodes further down the tree.
+ * @return
+ * A String containing an XCQL document equivalent to the
+ * parse-tree whose root is this node.
+ */
+ public String toXCQL(int level) {
+ return toXCQL(level, null);
+ }
+
+ public String toXCQL(int level, Vector<CQLPrefix> prefixes) {
+ return toXCQL(level, prefixes, null);
+ }
+
+ abstract public String toXCQL(int level, Vector<CQLPrefix> prefixes,
+ Vector<ModifierSet> sortkeys);
+
+ protected static String renderPrefixes(int level, Vector prefixes) {
+ if (prefixes == null || prefixes.size() == 0)
+ return "";
+ String res = indent(level) + "<prefixes>\n";
+ for (int i = 0; i < prefixes.size(); i++) {
+ CQLPrefix p = (CQLPrefix) prefixes.get(i);
+ res += indent(level+1) + "<prefix>\n";
+ if (p.name != null)
+ res += indent(level+2) + "<name>" + p.name + "</name>\n";
+ res += indent(level+2) +
+ "<identifier>" + p.identifier + "</identifier>\n";
+ res += indent(level+1) + "</prefix>\n";
+ }
+ return res + indent(level) + "</prefixes>\n";
+ }
+
+ protected static String renderSortKeys(int level,
+ Vector<ModifierSet> sortkeys) {
+ if (sortkeys == null || sortkeys.size() == 0)
+ return "";
+ String res = indent(level) + "<sortKeys>\n";
+ for (int i = 0; i < sortkeys.size(); i++) {
+ ModifierSet key = sortkeys.get(i);
+ res += key.sortKeyToXCQL(level+1);
+ }
+ return res + indent(level) + "</sortKeys>\n";
+ }
+
+ /**
+ * Decompiles a parse-tree into a CQL query.
+ * <P>
+ * @return
+ * A String containing a CQL query equivalent to the parse-tree
+ * whose root is this node, so that compiling that query will
+ * yield an identical tree.
+ */
+ abstract public String toCQL();
+
+ /**
+ * Renders a parse-tree into a Yaz-style PQF string.
+ * PQF, or Prefix Query Format, is a cryptic but powerful notation
+ * that can be trivially mapped, one-to-one, int Z39.50 Type-1 and
+ * Type-101 queries. A specification for the format can be found
+ * in
+ * <A href="http://indexdata.dk/yaz/doc/tools.php#PQF"
+ * >Chapter 7 (Supporting Tools)</A> of the
+ * <A href="http://indexdata.dk/yaz/">YAZ</A> manual.
+ * <P>
+ * @param config
+ * A <TT>Properties</TT> object containing configuration
+ * information that specifies the mapping from CQL indexes,
+ * relations, etc. to Type-1 attributes. The mapping
+ * specification is described in the CQL-Java distribution's
+ * sample PQF-mapping configuration file,
+ * <TT>etc/pqf.properties</TT>, which see.
+ * @return
+ * A String containing a PQF query equivalent to the parse-tree
+ * whose root is this node.
+ */
+ abstract public String toPQF(Properties config)
+ throws PQFTranslationException;
+
+ /**
+ * Returns a String of spaces for indenting to the specified level.
+ */
+ protected static String indent(int level) { return Utils.indent(level); }
+
+ /**
+ * Returns the argument String quoted for XML.
+ * For example, each occurrence of <TT><</TT> is translated to
+ * <TT>&lt;</TT>.
+ */
+ protected static String xq(String str) { return Utils.xq(str); }
+
+ /**
+ * Renders a parser-tree into a BER-endoded packet representing an
+ * equivalent Z39.50 Type-1 query. If you don't know what that
+ * means, then you don't need this method :-) This is useful
+ * primarily for SRW-to-Z39.50 gateways.
+ *
+ * @param config
+ * A <TT>Properties</TT> object containing configuration
+ * information that specifies the mapping from CQL indexes,
+ * relations, etc. to Type-1 attributes. The mapping
+ * specification is described in the CQL-Java distribution's
+ * sample PQF-mapping configuration file,
+ * <TT>etc/pqf.properties</TT>, which see.
+ * @return
+ * A byte array containing the BER packet.
+ * @see
+ * <A href="ftp://ftp.rsasecurity.com/pub/pkcs/ascii/layman.asc"
+ * >ftp://ftp.rsasecurity.com/pub/pkcs/ascii/layman.asc</A>
+ */
+ abstract public byte[] toType1BER(Properties config)
+ throws PQFTranslationException;
+
+ // ANS.1 classes
+ protected static final int UNIVERSAL = 0;
+ protected static final int APPLICATION = 1;
+ protected static final int CONTEXT = 2;
+ protected static final int PRIVATE = 3;
+
+ // ASN.1 tag forms
+ protected static final int PRIMITIVE = 0;
+ protected static final int CONSTRUCTED = 1;
+
+ // ASN.1 UNIVERSAL data types
+ public static final byte BOOLEAN = 1;
+ public static final byte INTEGER = 2;
+ public static final byte BITSTRING = 3;
+ public static final byte OCTETSTRING = 4;
+ public static final byte NULL = 5;
+ public static final byte OBJECTIDENTIFIER = 6;
+ public static final byte OBJECTDESCRIPTOR = 7;
+ public static final byte EXTERNAL = 8;
+ public static final byte ENUMERATED = 10;
+ public static final byte SEQUENCE = 16;
+ public static final byte SET = 17;
+ public static final byte VISIBLESTRING = 26;
+ public static final byte GENERALSTRING = 27;
+
+ protected static final int putTag(int asn1class, int fldid, int form,
+ byte[] record, int offset) {
+ if (fldid < 31)
+ record[offset++] = (byte)(fldid + asn1class*64 + form*32);
+ else {
+ record[offset++] = (byte)(31 + asn1class*64 + form*32);
+ if (fldid < 128)
+ record[offset++] = (byte)(fldid);
+ else {
+ record[offset++] = (byte)(128 + fldid/128);
+ record[offset++] = (byte)(fldid % 128);
+ }
+ }
+ return offset;
+ }
+
+ /**
+ * Put a length directly into a BER record.
+ *
+ * @param len length to put into record
+ * @return the new, incremented value of the offset parameter.
+ */
+ public // ### shouldn't this be protected?
+ static final int putLen(int len, byte[] record, int offset) {
+
+ if (len < 128)
+ record[offset++] = (byte)len;
+ else {
+ int t;
+ record[offset] = (byte)(lenLen(len) - 1);
+ for (t = record[offset]; t > 0; t--) {
+ record[offset+t] = (byte)(len & 0xff);
+ len >>= 8;
+ }
+ t = offset;
+ offset += (record[offset]&0xff) + 1;
+ record[t] += 128; // turn on bit 8 in length byte.
+ }
+ return offset;
+ }
+
+ /**
+ * Get the length needed to represent the given length.
+ *
+ * @param length determine length needed to encode this
+ * @return length needed to encode given length
+ */
+ protected // ### shouldn't this be private?
+ static final int lenLen(int length) {
+
+ return ((length < 128) ? 1 :
+ (length < 256) ? 2 :
+ (length < 65536L) ? 3 : 4);
+ }
+
+ /**
+ * Get the length needed to represent the given number.
+ *
+ * @param num determine length needed to encode this
+ * @return length needed to encode given number
+ */
+ protected static final int numLen(long num) {
+ num = num < 0 ? -num : num;
+ // ### Wouldn't this be better done algorithmically?
+ // Or at least with the constants expressed in hex?
+ return ((num < 128) ? 1 :
+ (num < 32768) ? 2 :
+ (num < 8388608) ? 3 :
+ (num < 2147483648L) ? 4 :
+ (num < 549755813888L) ? 5 :
+ (num < 140737488355328L) ? 6 :
+ (num < 36028797018963968L) ? 7 : 8);
+ }
+
+ /**
+ * Put a number into a given buffer
+ *
+ * @param num number to put into buffer
+ * @param record buffer to use
+ * @param offset offset into buffer
+ * @return the new, incremented value of the offset parameter.
+ */
+ protected static final int putNum(long num, byte record[], int offset) {
+ int cnt=numLen(num);
+
+ for (int count = cnt - 1; count >= 0; count--) {
+ record[offset+count] = (byte)(num & 0xff);
+ num >>= 8;
+ }
+ return offset+cnt;
+ }
+
+ // Used only by the makeOID() method
+ private static final java.util.Hashtable<String, byte[]> madeOIDs =
+ new java.util.Hashtable<String, byte[]>(10);
+
+ protected static final byte[] makeOID(String oid) {
+ byte[] o;
+ int dot, offset = 0, oidOffset = 0, value;
+
+ if ((o = (byte[])madeOIDs.get(oid)) == null) {
+ o = new byte[100];
+
+ // Isn't this kind of thing excruciating in Java?
+ while (oidOffset < oid.length() &&
+ Character.isDigit(oid.charAt(oidOffset)) == true) {
+ if (offset > 90) // too large
+ return null;
+
+ dot = oid.indexOf('.', oidOffset);
+ if (dot == -1)
+ dot = oid.length();
+
+ value = Integer.parseInt(oid.substring(oidOffset, dot));
+
+ if (offset == 0) { // 1st two are special
+ if (dot == -1) // ### can't happen: -1 is reassigned above
+ return null; // can't be this short
+ oidOffset = dot+1; // skip past '.'
+
+ dot = oid.indexOf('.', oidOffset);
+ if (dot == -1)
+ dot = oid.length();
+
+ // ### Eh?!
+ value = value * 40 +
+ Integer.parseInt(oid.substring(oidOffset,dot));
+ }
+
+ if (value < 0x80) {
+ o[offset++] = (byte)value;
+ } else {
+ int count = 0;
+ byte bits[] = new byte[12]; // save a 84 (12*7) bit number
+
+ while (value != 0) {
+ bits[count++] = (byte)(value & 0x7f);
+ value >>= 7;
+ }
+
+ // Now place in the correct order
+ while (--count > 0)
+ o[offset++] = (byte)(bits[count] | 0x80);
+
+ o[offset++] = bits[count];
+ }
+
+ dot = oid.indexOf('.', oidOffset);
+ if (dot == -1)
+ break;
+
+ oidOffset = dot+1;
+ }
+
+ byte[] ptr = new byte[offset];
+ System.arraycopy(o, 0, ptr, 0, offset);
+ madeOIDs.put(oid, ptr);
+ return ptr;
+ }
+ return o;
+ }
+
+ public static final byte[] makeQuery(CQLNode root, Properties properties)
+ throws PQFTranslationException {
+ byte[] rpnStructure = root.toType1BER(properties);
+ byte[] qry = new byte[rpnStructure.length+100];
+ int offset = 0;
+ offset = putTag(CONTEXT, 1, CONSTRUCTED, qry, offset);
+ qry[offset++] = (byte)(0x80&0xff); // indefinite length
+ offset = putTag(UNIVERSAL, OBJECTIDENTIFIER, PRIMITIVE, qry, offset);
+ byte[] oid = makeOID("1.2.840.10003.3.1"); // bib-1
+ offset = putLen(oid.length, qry, offset);
+ System.arraycopy(oid, 0, qry, offset, oid.length);
+ offset += oid.length;
+ System.arraycopy(rpnStructure, 0, qry, offset, rpnStructure.length);
+ offset += rpnStructure.length;
+ qry[offset++] = 0x00; // end of query
+ qry[offset++] = 0x00;
+ byte[] q = new byte[offset];
+ System.arraycopy(qry, 0, q, 0, offset);
+ return q;
+ }
+}
--- /dev/null
+// $Id: CQLNotNode.java,v 1.9 2007-06-29 12:48:21 mike Exp $
+
+package org.z3950.zing.cql;
+
+
+/**
+ * Represents a NOT node in a CQL parse-tree.
+ *
+ * @version $Id: CQLNotNode.java,v 1.9 2007-06-29 12:48:21 mike Exp $
+ */
+public class CQLNotNode extends CQLBooleanNode {
+ /**
+ * Creates a new NOT node with the specified left- and right-hand
+ * sides and modifiers.
+ */
+ public CQLNotNode(CQLNode left, CQLNode right, ModifierSet ms) {
+ super(left, right, ms);
+ }
+
+ byte[] opType1() {
+ byte[] op = new byte[5];
+ putTag(CONTEXT, 46, CONSTRUCTED, op, 0); // Operator
+ putLen(2, op, 2);
+ putTag(CONTEXT, 2, PRIMITIVE, op, 3); // and-not
+ putLen(0, op, 4);
+ return op;
+ }
+}
--- /dev/null
+// $Id: CQLOrNode.java,v 1.9 2007-06-29 12:48:21 mike Exp $
+
+package org.z3950.zing.cql;
+
+
+/**
+ * Represents an OR node in a CQL parse-tree.
+ *
+ * @version $Id: CQLOrNode.java,v 1.9 2007-06-29 12:48:21 mike Exp $
+ */
+public class CQLOrNode extends CQLBooleanNode {
+ /**
+ * Creates a new OR node with the specified left- and right-hand
+ * sides and modifiers.
+ */
+ public CQLOrNode(CQLNode left, CQLNode right, ModifierSet ms) {
+ super(left, right, ms);
+ }
+
+ byte[] opType1() {
+ byte[] op = new byte[5];
+ putTag(CONTEXT, 46, CONSTRUCTED, op, 0); // Operator
+ putLen(2, op, 2);
+ putTag(CONTEXT, 1, PRIMITIVE, op, 3); // or
+ putLen(0, op, 4);
+ return op;
+ }
+}
--- /dev/null
+// $Id: CQLParseException.java,v 1.2 2002-11-06 20:13:45 mike Exp $
+
+package org.z3950.zing.cql;
+import java.lang.Exception;
+
+
+/**
+ * Exception indicating that an error ocurred parsing CQL.
+ *
+ * @version $Id: CQLParseException.java,v 1.2 2002-11-06 20:13:45 mike Exp $
+ */
+public class CQLParseException extends Exception {
+ /**
+ * Creates a new <TT>CQLParseException</TT>.
+ * @param s
+ * An error message describing the problem with the query,
+ * usually a syntax error of some kind.
+ */
+ public CQLParseException(String s) {
+ super(s);
+ }
+}
+
--- /dev/null
+// $Id: CQLParser.java,v 1.39 2007-08-06 15:54:48 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 CQL strings into parse trees of CQLNode subtypes.
+ *
+ * @version $Id: CQLParser.java,v 1.39 2007-08-06 15:54:48 mike Exp $
+ * @see <A href="http://zing.z3950.org/cql/index.html"
+ * >http://zing.z3950.org/cql/index.html</A>
+ */
+public class CQLParser {
+ private CQLLexer lexer;
+ private int compat; // When false, implement CQL 1.2
+ public static int V1POINT1 = 12368;
+ public static int V1POINT2 = 12369;
+ public static int V1POINT1SORT = 12370;
+
+ static private boolean DEBUG = false;
+ static private boolean LEXDEBUG = false;
+
+ /**
+ * The new parser implements a dialect of CQL specified by the
+ * <tt>compat</tt> argument:
+ * <ul>
+ * <li>V1POINT1 - CQL version 1.1
+ * </li>
+ * <li>V1POINT2 - CQL version 1.2
+ * </li>
+ * <li>V1POINT1SORT - CQL version 1.1 but including
+ * <tt>sortby</tt> as specified for CQL 1.2.
+ * </li>
+ * </ul>
+ */
+ public CQLParser(int compat) {
+ this.compat = compat;
+ }
+
+ /**
+ * The new parser implements CQL 1.2
+ */
+ public CQLParser() {
+ this.compat = V1POINT2;
+ }
+
+ private static void debug(String str) {
+ if (DEBUG)
+ 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; <TT>toXCQL()</TT> returns an
+ * XML snippet representing the query; and <TT>toPQF()</TT>
+ * returns the query rendered in Index Data's Prefix Query
+ * Format.
+ *
+ * @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);
+
+ lexer.nextToken();
+ debug("about to parseQuery()");
+ CQLNode root = parseTopLevelPrefixes("cql.serverChoice",
+ new CQLRelation(compat == V1POINT2 ? "=" : "scr"));
+ if (lexer.ttype != lexer.TT_EOF)
+ throw new CQLParseException("junk after end: " + lexer.render());
+
+ return root;
+ }
+
+ private CQLNode parseTopLevelPrefixes(String index, CQLRelation relation)
+ throws CQLParseException, IOException {
+ debug("top-level prefix mapping");
+
+ if (lexer.ttype == '>') {
+ return parsePrefix(index, relation, true);
+ }
+
+ CQLNode node = parseQuery(index, relation);
+ if ((compat == V1POINT2 || compat == V1POINT1SORT) &&
+ lexer.ttype == lexer.TT_SORTBY) {
+ match(lexer.ttype);
+ debug("sortspec");
+
+ CQLSortNode sortnode = new CQLSortNode(node);
+ while (lexer.ttype != lexer.TT_EOF) {
+ String sortindex = matchSymbol("sort index");
+ ModifierSet ms = gatherModifiers(sortindex);
+ sortnode.addSortIndex(ms);
+ }
+
+ if (sortnode.keys.size() == 0) {
+ throw new CQLParseException("no sort keys");
+ }
+
+ node = sortnode;
+ }
+
+ return node;
+ }
+
+ private CQLNode parseQuery(String index, CQLRelation relation)
+ throws CQLParseException, IOException {
+ debug("in parseQuery()");
+
+ CQLNode term = parseTerm(index, relation);
+ while (lexer.ttype != lexer.TT_EOF &&
+ lexer.ttype != ')' &&
+ lexer.ttype != lexer.TT_SORTBY) {
+ if (lexer.ttype == lexer.TT_AND ||
+ lexer.ttype == lexer.TT_OR ||
+ lexer.ttype == lexer.TT_NOT ||
+ lexer.ttype == lexer.TT_PROX) {
+ int type = lexer.ttype;
+ String val = lexer.sval;
+ match(type);
+ ModifierSet ms = gatherModifiers(val);
+ CQLNode term2 = parseTerm(index, relation);
+ term = ((type == lexer.TT_AND) ? new CQLAndNode(term, term2, ms) :
+ (type == lexer.TT_OR) ? new CQLOrNode (term, term2, ms) :
+ (type == lexer.TT_NOT) ? new CQLNotNode(term, term2, ms) :
+ new CQLProxNode(term, term2, ms));
+ } else {
+ throw new CQLParseException("expected boolean, got " +
+ lexer.render());
+ }
+ }
+
+ debug("no more ops");
+ return term;
+ }
+
+ private ModifierSet gatherModifiers(String base)
+ throws CQLParseException, IOException {
+ debug("in gatherModifiers()");
+
+ ModifierSet ms = new ModifierSet(base);
+ while (lexer.ttype == '/') {
+ match('/');
+ if (lexer.ttype != lexer.TT_WORD)
+ throw new CQLParseException("expected modifier, "
+ + "got " + lexer.render());
+ String type = lexer.sval.toLowerCase();
+ match(lexer.ttype);
+ if (!isRelation()) {
+ // It's a simple modifier consisting of type only
+ ms.addModifier(type);
+ } else {
+ // It's a complex modifier of the form type=value
+ String comparision = lexer.render(lexer.ttype, false);
+ match(lexer.ttype);
+ String value = matchSymbol("modifier value");
+ ms.addModifier(type, comparision, value);
+ }
+ }
+
+ return ms;
+ }
+
+ private CQLNode parseTerm(String index, CQLRelation relation)
+ throws CQLParseException, IOException {
+ debug("in parseTerm()");
+
+ String word;
+ while (true) {
+ if (lexer.ttype == '(') {
+ debug("parenthesised term");
+ match('(');
+ CQLNode expr = parseQuery(index, relation);
+ match(')');
+ return expr;
+ } else if (lexer.ttype == '>') {
+ return parsePrefix(index, relation, false);
+ }
+
+ debug("non-parenthesised term");
+ word = matchSymbol("index or term");
+ if (!isRelation() && lexer.ttype != lexer.TT_WORD)
+ break;
+
+ index = word;
+ String relstr = (lexer.ttype == lexer.TT_WORD ?
+ lexer.sval : lexer.render(lexer.ttype, false));
+ relation = new CQLRelation(relstr);
+ match(lexer.ttype);
+ ModifierSet ms = gatherModifiers(relstr);
+ relation.setModifiers(ms);
+ debug("index='" + index + ", " +
+ "relation='" + relation.toCQL() + "'");
+ }
+
+ CQLTermNode node = new CQLTermNode(index, relation, word);
+ debug("made term node " + node.toCQL());
+ return node;
+ }
+
+ private CQLNode parsePrefix(String index, CQLRelation relation,
+ boolean topLevel)
+ throws CQLParseException, IOException {
+ debug("prefix mapping");
+
+ match('>');
+ String name = null;
+ String identifier = matchSymbol("prefix-name");
+ if (lexer.ttype == '=') {
+ match('=');
+ name = identifier;
+ identifier = matchSymbol("prefix-identifer");
+ }
+ CQLNode node = topLevel ?
+ parseTopLevelPrefixes(index, relation) :
+ parseQuery(index, relation);
+
+ return new CQLPrefixNode(name, identifier, node);
+ }
+
+ // Checks for a relation
+ private boolean isRelation() {
+ debug("isRelation: checking ttype=" + lexer.ttype +
+ " (" + lexer.render() + ")");
+ return (lexer.ttype == '<' ||
+ lexer.ttype == '>' ||
+ lexer.ttype == '=' ||
+ lexer.ttype == lexer.TT_LE ||
+ lexer.ttype == lexer.TT_GE ||
+ lexer.ttype == lexer.TT_NE ||
+ lexer.ttype == lexer.TT_EQEQ);
+ }
+
+ private void match(int token)
+ throws CQLParseException, IOException {
+ debug("in match(" + lexer.render(token, true) + ")");
+ if (lexer.ttype != token)
+ throw new CQLParseException("expected " +
+ lexer.render(token, true) +
+ ", " + "got " + lexer.render());
+ int tmp = lexer.nextToken();
+ debug("match() got token=" + lexer.ttype + ", " +
+ "nval=" + lexer.nval + ", sval='" + lexer.sval + "'" +
+ " (tmp=" + tmp + ")");
+ }
+
+ private String matchSymbol(String expected)
+ throws CQLParseException, IOException {
+
+ debug("in matchSymbol()");
+ if (lexer.ttype == lexer.TT_WORD ||
+ lexer.ttype == lexer.TT_NUMBER ||
+ lexer.ttype == '"' ||
+ // The following is a complete list of keywords. Because
+ // they're listed here, they can be used unquoted as
+ // 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.
+ lexer.ttype == lexer.TT_AND ||
+ lexer.ttype == lexer.TT_OR ||
+ lexer.ttype == lexer.TT_NOT ||
+ lexer.ttype == lexer.TT_PROX ||
+ lexer.ttype == lexer.TT_SORTBY) {
+ String symbol = (lexer.ttype == lexer.TT_NUMBER) ?
+ lexer.render() : lexer.sval;
+ match(lexer.ttype);
+ return symbol;
+ }
+
+ throw new CQLParseException("expected " + expected + ", " +
+ "got " + lexer.render());
+ }
+
+
+ /**
+ * 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>
+ * <triple>
+ * <boolean>
+ * <value>and</value>
+ * </boolean>
+ * <triple>
+ * <boolean>
+ * <value>or</value>
+ * </boolean>
+ * <searchClause>
+ * <index>au</index>
+ * <relation>
+ * <value>=</value>
+ * </relation>
+ * <term>Kerninghan</term>
+ * </searchClause>
+ * <searchClause>
+ * <index>au</index>
+ * <relation>
+ * <value>=</value>
+ * </relation>
+ * <term>Ritchie</term>
+ * </searchClause>
+ * </triple>
+ * <searchClause>
+ * <index>ti</index>
+ * <relation>
+ * <value>=</value>
+ * </relation>
+ * <term>Unix</term>
+ * </searchClause>
+ * </triple>
+ * </PRE>
+ * <P>
+ * @param -1
+ * CQL version 1.1 (default version 1.2)
+ * @param -d
+ * Debug mode: extra output written to stderr.
+ * @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) {
+ char mode = 'x'; // x=XCQL, c=CQL, p=PQF
+ String pfile = null;
+
+ Vector<String> argv = new Vector<String>();
+ for (int i = 0; i < args.length; i++) {
+ argv.add(args[i]);
+ }
+
+ int compat = V1POINT2;
+ if (argv.size() > 0 && argv.get(0).equals("-1")) {
+ compat = V1POINT1;
+ argv.remove(0);
+ }
+
+ if (argv.size() > 0 && argv.get(0).equals("-d")) {
+ DEBUG = true;
+ argv.remove(0);
+ }
+
+ 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 [-1] [-d] [-c] " +
+ "[-p <pqf-properties> [<CQL-query>]");
+ System.err.println("If unspecified, query is read from stdin");
+ System.exit(1);
+ }
+
+ String cql;
+ if (argv.size() == 1) {
+ cql = (String) argv.get(0);
+ } else {
+ byte[] bytes = new byte[10000];
+ try {
+ // Read in the whole of standard input in one go
+ int nbytes = System.in.read(bytes);
+ } catch (IOException ex) {
+ System.err.println("Can't read query: " + ex.getMessage());
+ System.exit(2);
+ }
+ cql = new String(bytes);
+ }
+
+ CQLParser parser = new CQLParser(compat);
+ CQLNode root = null;
+ try {
+ root = parser.parse(cql);
+ } catch (CQLParseException ex) {
+ System.err.println("Syntax error: " + ex.getMessage());
+ System.exit(3);
+ } catch (IOException ex) {
+ 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 (IOException ex) {
+ System.err.println("Can't render query: " + ex.getMessage());
+ System.exit(5);
+ } catch (UnknownIndexException ex) {
+ System.err.println("Unknown index: " + ex.getMessage());
+ System.exit(6);
+ } catch (UnknownRelationException ex) {
+ System.err.println("Unknown relation: " + ex.getMessage());
+ System.exit(7);
+ } catch (UnknownRelationModifierException ex) {
+ System.err.println("Unknown relation modifier: " +
+ ex.getMessage());
+ System.exit(8);
+ } catch (UnknownPositionException ex) {
+ System.err.println("Unknown position: " + ex.getMessage());
+ System.exit(9);
+ } catch (PQFTranslationException ex) {
+ // We catch all of this class's subclasses, so --
+ throw new Error("can't get a PQFTranslationException");
+ }
+ }
+}
--- /dev/null
+// $Id: CQLPrefix.java,v 1.5 2007-06-27 22:39:55 mike Exp $
+
+package org.z3950.zing.cql;
+import java.lang.String;
+
+/**
+ * Represents a CQL prefix mapping from short name to long identifier.
+ *
+ * @version $Id: CQLPrefix.java,v 1.5 2007-06-27 22:39:55 mike Exp $
+ */
+public class CQLPrefix {
+ /**
+ * The short name of the prefix mapping. That is, the prefix
+ * itself, such as <TT>dc</TT>, as it might be used in an index
+ * like <TT>dc.title</TT>.
+ */
+ public String name;
+
+ /**
+ * The full identifier name of the prefix mapping. That is,
+ * typically, a URI permanently allocated to a specific index
+ * set, such as <TT>http://zthes.z3950.org/cql/1.0<TT>.
+ */
+ public String identifier;
+
+ /**
+ * Creates a new CQLPrefix mapping, which maps the specified name
+ * to the specified identifier.
+ */
+ CQLPrefix(String name, String identifier) {
+ this.name = name;
+ this.identifier = identifier;
+ }
+}
--- /dev/null
+// $Id: CQLPrefixNode.java,v 1.10 2007-07-03 16:40:11 mike Exp $
+
+package org.z3950.zing.cql;
+import java.lang.String;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ * Represents a prefix node in a CQL parse-tree.
+ *
+ * @version $Id: CQLPrefixNode.java,v 1.10 2007-07-03 16:40:11 mike Exp $
+ */
+public class CQLPrefixNode extends CQLNode {
+ /**
+ * The prefix definition that governs the subtree.
+ */
+ public CQLPrefix prefix;
+
+ /**
+ * The root of a parse-tree representing the part of the query
+ * that is governed by this prefix definition.
+ */
+ public CQLNode subtree;
+
+ /**
+ * Creates a new CQLPrefixNode inducing a mapping from the
+ * specified index-set name to the specified identifier across
+ * the specified subtree.
+ */
+ public CQLPrefixNode(String name, String identifier, CQLNode subtree) {
+ this.prefix = new CQLPrefix(name, identifier);
+ this.subtree = subtree;
+ }
+
+ public String toXCQL(int level, Vector<CQLPrefix> prefixes,
+ Vector<ModifierSet> sortkeys) {
+ Vector<CQLPrefix> tmp = (prefixes == null ?
+ new Vector<CQLPrefix>() :
+ new Vector<CQLPrefix>(prefixes));
+ tmp.add(prefix);
+ return subtree.toXCQL(level, tmp, sortkeys);
+ }
+
+ public String toCQL() {
+ // ### We don't always need parens around the subtree
+ if (prefix.name == null) {
+ return ">\"" + prefix.identifier + "\" " +
+ "(" + subtree.toCQL() + ")";
+ } else {
+ return ">" + prefix.name + "=\"" + prefix.identifier + "\" " +
+ "(" + subtree.toCQL() + ")";
+ }
+ }
+
+ public String toPQF(Properties config) throws PQFTranslationException {
+ // Prefixes and their identifiers don't actually play any role
+ // in PQF translation, since the meanings of the indexes,
+ // including their prefixes if any, are instead wired into
+ // `config'.
+ return subtree.toPQF(config);
+ }
+
+ public byte[] toType1BER(Properties config) throws PQFTranslationException {
+ // See comment on toPQF()
+ return subtree.toType1BER(config);
+ }
+}
--- /dev/null
+// $Id: CQLProxNode.java,v 1.14 2007-06-29 12:53:03 mike Exp $
+
+package org.z3950.zing.cql;
+
+
+/**
+ * Represents a proximity node in a CQL parse-tree.
+ * The left- and right-hand-sides must be satisfied by parts of the
+ * candidate records which are sufficiently close to each other, as
+ * specified by a set of proximity parameters.
+ *
+ * @version $Id: CQLProxNode.java,v 1.14 2007-06-29 12:53:03 mike Exp $
+ */
+public class CQLProxNode extends CQLBooleanNode {
+ /**
+ * Creates a new PROX node with the specified left- and right-hand
+ * sides and modifiers.
+ */
+ public CQLProxNode(CQLNode left, CQLNode right, ModifierSet ms) {
+ super(left, right, ms);
+ }
+
+ /*
+ * proximity ::= exclusion distance ordered relation which-code unit-code.
+ * exclusion ::= '1' | '0' | 'void'.
+ * distance ::= integer.
+ * ordered ::= '1' | '0'.
+ * relation ::= integer.
+ * which-code ::= 'known' | 'private' | integer.
+ * unit-code ::= integer.
+ */
+ String opPQF() {
+ int relCode = getRelCode();
+ int unitCode = getProxUnitCode();
+
+ String res = "prox " +
+ "0 " +
+ ms.modifier("distance") + " " +
+ (ms.modifier("ordering").equals("ordered") ? 1 : 0) + " " +
+ relCode + " " +
+ "1 " +
+ unitCode;
+
+ return res;
+ }
+
+ private int getRelCode() {
+ String rel = ms.modifier("relation");
+ if (rel.equals("<")) {
+ return 1;
+ } else if (rel.equals("<=")) {
+ return 2;
+ } else if (rel.equals("=")) {
+ return 3;
+ } else if (rel.equals(">=")) {
+ return 4;
+ } else if (rel.equals(">")) {
+ return 5;
+ } else if (rel.equals("<>")) {
+ return 6;
+ }
+ return 0;
+ }
+
+ private int getProxUnitCode() {
+ String unit = ms.modifier("unit");
+ if (unit.equals("word")) {
+ return 2;
+ } else if (unit.equals("sentence")) {
+ return 3;
+ } else if (unit.equals("paragraph")) {
+ return 4;
+ } else if (unit.equals("element")) {
+ return 8;
+ }
+ return 0;
+ }
+
+
+ byte[] opType1() {
+ byte[] op = new byte[100];
+ int offset, value;
+ offset = putTag(CONTEXT, 46, CONSTRUCTED, op, 0); // Operator
+ op[offset++] = (byte)(0x80&0xff); // indefinite length
+
+ offset = putTag(CONTEXT, 3, CONSTRUCTED, op, offset); // prox
+ op[offset++] = (byte)(0x80&0xff); // indefinite length
+
+ offset = putTag(CONTEXT, 1, PRIMITIVE, op, offset); // exclusion
+ value = 0; // false
+ offset = putLen(numLen(value), op, offset);
+ offset = putNum(value, op, offset);
+
+ offset = putTag(CONTEXT, 2, PRIMITIVE, op, offset); // distance
+ value = Integer.parseInt(ms.modifier("distance"));
+ offset = putLen(numLen(value), op, offset);
+ offset = putNum(value, op, offset);
+
+ offset = putTag(CONTEXT, 3, PRIMITIVE, op, offset); // ordered
+ value = ms.modifier("ordering").equals("ordered") ? 1 : 0;
+ offset = putLen(numLen(value), op, offset);
+ offset = putNum(value, op, offset);
+
+ offset = putTag(CONTEXT, 4, PRIMITIVE, op, offset); // relationType
+ value = getRelCode();
+ offset = putLen(numLen(value), op, offset);
+ offset = putNum(value, op, offset);
+
+ offset = putTag(CONTEXT, 5, CONSTRUCTED, op, offset); // proximityUnitCode
+ op[offset++] = (byte)(0x80&0xff); // indefinite length
+ offset = putTag(CONTEXT, 1, PRIMITIVE, op, offset); // known
+ value = getProxUnitCode();
+ offset = putLen(numLen(value), op, offset);
+ offset = putNum(value, op, offset);
+ op[offset++] = 0x00; // end of proximityUnitCode
+ op[offset++] = 0x00;
+
+ op[offset++] = 0x00; // end of prox
+ op[offset++] = 0x00;
+ op[offset++] = 0x00; // end of Operator
+ op[offset++] = 0x00;
+
+ byte[] o = new byte[offset];
+ System.arraycopy(op, 0, o, 0, offset);
+ return o;
+ }
+}
--- /dev/null
+// $Id: CQLRelation.java,v 1.19 2007-07-03 13:40:58 mike Exp $
+
+package org.z3950.zing.cql;
+import java.util.Vector;
+import java.util.Properties;
+import java.lang.StringBuffer;
+
+/**
+ * Represents a relation between a CQL index and term.
+ *
+ * @version $Id: CQLRelation.java,v 1.19 2007-07-03 13:40:58 mike Exp $
+ */
+public class CQLRelation extends CQLNode {
+ ModifierSet ms;
+
+ /**
+ * Creates a new CQLRelation with the specified base relation.
+ * Typical base relations include the usual six ordering relations
+ * (<TT><=</TT>, <TT>></TT>, <I>etc.</I>), the text
+ * relations <TT>any</TT>, <TT>all</TT> and <TT>exact</TT>, the
+ * old server-choice relation <TT>scr</TT> and profiled relations of
+ * the form <TT><I>prefix</I>.<I>name</I></TT>.
+ */
+ // ### Seems wrong: a modifier set should not have a base, a
+ // relation should
+ public CQLRelation(String base) {
+ ms = new ModifierSet(base);
+ }
+
+ /**
+ * Returns the base relation with which the CQLRelation was
+ * originally created.
+ */
+ public String getBase() {
+ return ms.getBase();
+ }
+
+ /**
+ * Sets the modifiers of the specified CQLRelation.
+ * Typical relation modifiers include <TT>relevant</TT>,
+ * <TT>fuzzy</TT>, <TT>stem</TT> and <TT>phonetic</TT>. On the
+ * whole, these modifiers have a meaningful interpretation only
+ * for the text relations.
+ */
+ public void setModifiers(ModifierSet ms) {
+ this.ms = ms;
+ }
+
+ /**
+ * Returns an array of the modifiers associated with a CQLRelation.
+ * @return
+ * An array of Modifier objects.
+ */
+ public Vector<Modifier> getModifiers() {
+ return ms.getModifiers();
+ }
+
+ public String toXCQL(int level, Vector prefixes, Vector sortkeys) {
+ if (sortkeys != null)
+ throw new Error("CQLRelation.toXCQL() called with sortkeys");
+
+ return ms.toXCQL(level, "relation");
+ }
+
+ public String toCQL() {
+ return ms.toCQL();
+ }
+
+ public String toPQF(Properties config) throws PQFTranslationException {
+ throw new Error("CQLRelation.toPQF() can never be called");
+ }
+
+ public byte[] toType1BER(Properties config) {
+ throw new Error("CQLRelation.toType1BER() can never be called");
+ }
+}
--- /dev/null
+// $Id: CQLSortNode.java,v 1.2 2008-04-11 12:05:15 mike Exp $
+
+package org.z3950.zing.cql;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ * Represents a sort node in a CQL parse-tree.
+ *
+ * @version $Id: CQLSortNode.java,v 1.2 2008-04-11 12:05:15 mike Exp $
+ */
+public class CQLSortNode extends CQLNode {
+ /**
+ * The root of a subtree representing the query whose result is to
+ * be sorted.
+ */
+ public CQLNode subtree;
+
+ /**
+ * The set of sort keys by which results are to be sorted,
+ * each expressed as an index together with zero or more
+ * modifiers.
+ */
+ Vector<ModifierSet> keys;
+
+ public CQLSortNode(CQLNode subtree) {
+ this.subtree = subtree;
+ keys = new Vector<ModifierSet>();
+ }
+
+ public void addSortIndex(ModifierSet key) {
+ keys.add(key);
+ }
+
+ public Vector<ModifierSet> getSortIndexes() {
+ return keys;
+ }
+
+ public String toXCQL(int level, Vector<CQLPrefix> prefixes,
+ Vector<ModifierSet> sortkeys) {
+ if (sortkeys != null)
+ throw new Error("CQLSortNode.toXCQL() called with sortkeys");
+ return subtree.toXCQL(level, prefixes, keys);
+ }
+
+ public String toCQL() {
+ StringBuffer buf = new StringBuffer(subtree.toCQL());
+
+ if (keys != null) {
+ buf.append(" sortby");
+ for (int i = 0; i < keys.size(); i++) {
+ ModifierSet key = keys.get(i);
+ buf.append(" " + key.toCQL());
+ }
+ }
+
+ return buf.toString();
+ }
+
+ public String toPQF(Properties config) throws PQFTranslationException {
+ return "@attr 1=oops \"###\"";
+ }
+
+ public byte[] toType1BER(Properties config)
+ throws PQFTranslationException {
+ // There is no way to represent sorting in a standard Z39.50
+ // Type-1 query, so the best we can do is return the
+ // underlying query and ignore the sort-specification.
+ return subtree.toType1BER(config);
+ }
+}
--- /dev/null
+// $Id: CQLTermNode.java,v 1.28 2007-07-03 13:41:24 mike Exp $
+
+package org.z3950.zing.cql;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ * Represents a terminal node in a CQL parse-tree.
+ * A term node consists of the term String itself, together with,
+ * optionally, an index string and a relation. Neither or both of
+ * these must be provided - you can't have an index without a
+ * relation or vice versa.
+ *
+ * @version $Id: CQLTermNode.java,v 1.28 2007-07-03 13:41:24 mike Exp $
+ */
+public class CQLTermNode extends CQLNode {
+ private String index;
+ private CQLRelation relation;
+ private String term;
+
+ /**
+ * Creates a new term node with the specified <TT>index</TT>,
+ * <TT>relation</TT> and <TT>term</TT>. The first two may be
+ * <TT>null</TT>, but the <TT>term</TT> may not.
+ */
+ public CQLTermNode(String index, CQLRelation relation, String term) {
+ this.index = index;
+ this.relation = relation;
+ this.term = term;
+ }
+
+ public String getIndex() { return index; }
+ public CQLRelation getRelation() { return relation; }
+ public String getTerm() { return term; }
+
+ private static boolean isResultSetIndex(String qual) {
+ return (qual.equals("srw.resultSet") ||
+ qual.equals("srw.resultSetId") ||
+ qual.equals("srw.resultSetName") ||
+ qual.equals("cql.resultSet") ||
+ qual.equals("cql.resultSetId") ||
+ qual.equals("cql.resultSetName"));
+ }
+
+ public String getResultSetName() {
+ if (isResultSetIndex(index))
+ return term;
+ else
+ return null;
+ }
+
+ public String toXCQL(int level, Vector<CQLPrefix> prefixes,
+ Vector<ModifierSet> sortkeys) {
+ return (indent(level) + "<searchClause>\n" +
+ renderPrefixes(level+1, prefixes) +
+ indent(level+1) + "<index>" + xq(index) + "</index>\n" +
+ relation.toXCQL(level+1) +
+ indent(level+1) + "<term>" + xq(term) + "</term>\n" +
+ renderSortKeys(level+1, sortkeys) +
+ indent(level) + "</searchClause>\n");
+ }
+
+ public String toCQL() {
+ String quotedIndex = maybeQuote(index);
+ String quotedTerm = maybeQuote(term);
+ String res = quotedTerm;
+
+ if (index != null &&
+ !index.equalsIgnoreCase("srw.serverChoice") &&
+ !index.equalsIgnoreCase("cql.serverChoice")) {
+ // ### We don't always need spaces around `relation'.
+ res = quotedIndex + " " + relation.toCQL() + " " + quotedTerm;
+ }
+
+ return res;
+ }
+
+ // ### Interaction between this and its callers is not good as
+ // regards truncation of the term and generation of truncation
+ // attributes. Change the interface to fix this.
+ private Vector getAttrs(Properties config) throws PQFTranslationException {
+ Vector<String> attrs = new Vector<String>();
+
+ // Do this first so that if any other truncation or
+ // completeness attributes are generated, they "overwrite"
+ // those specified here.
+ //
+ // ### This approach relies on an unpleasant detail of Index
+ // Data's (admittedly definitive) implementation of PQF,
+ // and should not relied upon.
+ //
+ String attr = config.getProperty("always");
+ if (attr != null)
+ attrs.add(attr);
+
+ attr = config.getProperty("index." + index);
+ if (attr == null)
+ throw new UnknownIndexException(index);
+ attrs.add(attr);
+
+ String rel = relation.getBase();
+ if (rel.equals("=")) {
+ rel = "eq";
+ } else if (rel.equals("<=")) {
+ rel = "le";
+ } else if (rel.equals(">=")) {
+ rel = "ge";
+ }
+ // ### Handling "any" and "all" properly would involve breaking
+ // the string down into a bunch of individual words and ORring
+ // or ANDing them together. Another day.
+ attr = config.getProperty("relation." + rel);
+ if (attr == null)
+ throw new UnknownRelationException(rel);
+ attrs.add(attr);
+
+ Vector<Modifier> mods = relation.getModifiers();
+ for (int i = 0; i < mods.size(); i++) {
+ String type = mods.get(i).type;
+ attr = config.getProperty("relationModifier." + type);
+ if (attr == null)
+ throw new UnknownRelationModifierException(type);
+ attrs.add(attr);
+ }
+
+ String pos = "any";
+ String text = term;
+ if (text.length() > 0 && text.substring(0, 1).equals("^")) {
+ text = text.substring(1); // ### change not seen by caller
+ pos = "first";
+ }
+ int len = text.length();
+ if (len > 0 && text.substring(len-1, len).equals("^")) {
+ text = text.substring(0, len-1); // ### change not seen by caller
+ pos = pos.equals("first") ? "firstAndLast" : "last";
+ // ### in the firstAndLast case, the standard
+ // pqf.properties file specifies that we generate a
+ // completeness=whole-field attributem, which means that
+ // we don't generate a position attribute at all. Do we
+ // care? Does it matter?
+ }
+
+ attr = config.getProperty("position." + pos);
+ if (attr == null)
+ throw new UnknownPositionException(pos);
+ attrs.add(attr);
+
+ attr = config.getProperty("structure." + rel);
+ if (attr == null)
+ attr = config.getProperty("structure.*");
+ attrs.add(attr);
+
+ return attrs;
+ }
+
+ public String toPQF(Properties config) throws PQFTranslationException {
+ if (isResultSetIndex(index)) {
+ // Special case: ignore relation, modifiers, wildcards, etc.
+ // There's parallel code in toType1BER()
+ return "@set " + maybeQuote(term);
+ }
+
+ Vector attrs = getAttrs(config);
+
+ String attr, s = "";
+ for (int i = 0; i < attrs.size(); i++) {
+ attr = (String) attrs.get(i);
+ s += "@attr " + Utils.replaceString(attr, " ", " @attr ") + " ";
+ }
+
+ String text = term;
+ if (text.length() > 0 && text.substring(0, 1).equals("^"))
+ text = text.substring(1);
+ int len = text.length();
+ if (len > 0 && text.substring(len-1, len).equals("^"))
+ text = text.substring(0, len-1);
+
+ return s + maybeQuote(text);
+ }
+
+ static String maybeQuote(String str) {
+ if (str == null)
+ return null;
+
+ // There _must_ be a better way to make this test ...
+ if (str.length() == 0 ||
+ str.indexOf('"') != -1 ||
+ str.indexOf(' ') != -1 ||
+ str.indexOf('\t') != -1 ||
+ str.indexOf('=') != -1 ||
+ str.indexOf('<') != -1 ||
+ str.indexOf('>') != -1 ||
+ str.indexOf('/') != -1 ||
+ str.indexOf('(') != -1 ||
+ str.indexOf(')') != -1) {
+ str = '"' + Utils.replaceString(str, "\"", "\\\"") + '"';
+ }
+
+ return str;
+ }
+
+ public byte[] toType1BER(Properties config) throws PQFTranslationException {
+ if (isResultSetIndex(index)) {
+ // Special case: ignore relation, modifiers, wildcards, etc.
+ // There's parallel code in toPQF()
+ byte[] operand = new byte[term.length()+100];
+ int offset;
+ offset = putTag(CONTEXT, 0, CONSTRUCTED, operand, 0); // op
+ operand[offset++] = (byte)(0x80&0xff); // indefinite length
+ offset = putTag(CONTEXT, 31, PRIMITIVE, operand, offset); // ResultSetId
+ byte[] t = term.getBytes();
+ offset = putLen(t.length, operand, offset);
+ System.arraycopy(t, 0, operand, offset, t.length);
+ offset += t.length;
+ operand[offset++] = 0x00; // end of Operand
+ operand[offset++] = 0x00;
+ byte[] o = new byte[offset];
+ System.arraycopy(operand, 0, o, 0, offset);
+ return o;
+ }
+
+ String text = term;
+ if (text.length() > 0 && text.substring(0, 1).equals("^"))
+ text = text.substring(1);
+ int len = text.length();
+ if (len > 0 && text.substring(len-1, len).equals("^"))
+ text = text.substring(0, len-1);
+
+ String attr, attrList, term = text;
+ byte[] operand = new byte[text.length()+100];
+ int i, j, offset, type, value;
+ offset = putTag(CONTEXT, 0, CONSTRUCTED, operand, 0); // op
+ operand[offset++]=(byte)(0x80&0xff); // indefinite length
+ offset = putTag(CONTEXT, 102, CONSTRUCTED, operand, offset); // AttributesPlusTerm
+ operand[offset++] = (byte)(0x80&0xff); // indefinite length
+ offset = putTag(CONTEXT, 44, CONSTRUCTED, operand, offset); // AttributeList
+ operand[offset++] = (byte)(0x80&0xff); // indefinite length
+
+ Vector attrs = getAttrs(config);
+ for(i = 0; i < attrs.size(); i++) {
+ attrList = (String) attrs.get(i);
+ java.util.StringTokenizer st =
+ new java.util.StringTokenizer(attrList);
+ while (st.hasMoreTokens()) {
+ attr = st.nextToken();
+ j = attr.indexOf('=');
+ offset = putTag(UNIVERSAL, SEQUENCE, CONSTRUCTED, operand, offset);
+ operand[offset++] = (byte)(0x80&0xff);
+ offset = putTag(CONTEXT, 120, PRIMITIVE, operand, offset);
+ type = Integer.parseInt(attr.substring(0, j));
+ offset = putLen(numLen(type), operand, offset);
+ offset = putNum(type, operand, offset);
+
+ offset = putTag(CONTEXT, 121, PRIMITIVE, operand, offset);
+ value = Integer.parseInt(attr.substring(j+1));
+ offset = putLen(numLen(value), operand, offset);
+ offset = putNum(value, operand, offset);
+ operand[offset++] = 0x00; // end of SEQUENCE
+ operand[offset++] = 0x00;
+ }
+ }
+ operand[offset++] = 0x00; // end of AttributeList
+ operand[offset++] = 0x00;
+
+ offset = putTag(CONTEXT, 45, PRIMITIVE, operand, offset); // general Term
+ byte[] t = term.getBytes();
+ offset = putLen(t.length, operand, offset);
+ System.arraycopy(t, 0, operand, offset, t.length);
+ offset += t.length;
+
+ operand[offset++] = 0x00; // end of AttributesPlusTerm
+ operand[offset++] = 0x00;
+ operand[offset++] = 0x00; // end of Operand
+ operand[offset++] = 0x00;
+ byte[] o = new byte[offset];
+ System.arraycopy(operand, 0, o, 0, offset);
+ return o;
+ }
+}
--- /dev/null
+# $Id: Makefile,v 1.18 2007-07-03 12:56:29 mike Exp $
+#
+# Your Java compiler will require that this source directory is on the
+# classpath. The best way to do that is just to add the CQL-Java
+# distribution's "src" subdirectory to your CLASSPATH environment
+# variable, like this:
+# CLASSPATH=$CLASSPATH:/where/ever/you/unpacked/it/cql-java-VERSION/src
+
+OBJ = Utils.class \
+ CQLNode.class CQLTermNode.class CQLBooleanNode.class \
+ CQLAndNode.class CQLOrNode.class CQLNotNode.class \
+ CQLProxNode.class CQLPrefixNode.class CQLSortNode.class \
+ CQLPrefix.class \
+ CQLRelation.class Modifier.class ModifierSet.class \
+ CQLParser.class CQLLexer.class CQLGenerator.class \
+ CQLParseException.class MissingParameterException.class \
+ PQFTranslationException.class \
+ UnknownIndexException.class UnknownRelationException.class \
+ UnknownRelationModifierException.class UnknownPositionException.class
+
+JARPATH = ../lib/cql-java.jar
+JAR = ../../../../$(JARPATH)
+$(JAR): $(OBJ)
+ cd ../../../..; jar cf $(JARPATH) org/z3950/zing/cql/*.class
+
+%.class: %.java
+ javac -Xlint:unchecked *.java
+
+test: $(JAR)
+ cd ../../../../../test/regression && make
+
+clean:
+ rm -f $(OBJ) 'CQLLexer$$Keyword.class'
+
--- /dev/null
+// $Id: MissingParameterException.java,v 1.2 2002-11-06 20:13:45 mike Exp $
+
+package org.z3950.zing.cql;
+import java.lang.Exception;
+
+
+/**
+ * Exception indicating that a required property was not specified.
+ *
+ * @version $Id: MissingParameterException.java,v 1.2 2002-11-06 20:13:45 mike Exp $
+ */
+public class MissingParameterException extends Exception {
+ /**
+ * Creates a new <TT>MissingParameterException</TT>.
+ * @param s
+ * The name of the property whose value was required but not supplied.
+ */
+ public MissingParameterException(String s) {
+ super(s);
+ }
+}
--- /dev/null
+// $Id: Modifier.java,v 1.4 2007-07-03 13:29:34 mike Exp $
+
+package org.z3950.zing.cql;
+import java.util.Vector;
+import java.lang.StringBuffer;
+
+/**
+ * Represents a single modifier, consisting of three elements: a type,
+ * a comparision and a value. For example, "distance", "<", "3". The
+ * type is mandatory; either the comparison and value must both occur,
+ * or neither must.
+ * <P>
+ * This class is used only by ModifierSet.
+ *
+ * @version $Id: Modifier.java,v 1.4 2007-07-03 13:29:34 mike Exp $
+ */
+public class Modifier {
+ String type;
+ String comparison;
+ String value;
+
+ /**
+ * Creates a new Modifier with the specified type, comparison
+ * and value.
+ */
+ public Modifier(String type, String comparison, String value) {
+ this.type = type;
+ this.comparison = comparison;
+ this.value = value;
+ //System.err.println("Made new modifier with " + "type='" + type + "', " + "comparison='" + comparison + "', " + "value='" + value + "',\n");
+ }
+
+ /**
+ * Creates a new Modifier with the specified type but no
+ * comparison or value.
+ */
+ public Modifier(String type) {
+ this.type = type;
+ //System.err.println("Made new modifier of type '" + type + "'\n");
+ }
+
+ /**
+ * Returns the type with which the Modifier was created.
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Returns the comparison with which the Modifier was created.
+ */
+ public String getComparison() {
+ return comparison;
+ }
+
+ /**
+ * Returns the value with which the Modifier was created.
+ */
+ public String getValue() {
+ return value;
+ }
+
+ public String toXCQL(int level, String relationElement) {
+ StringBuffer buf = new StringBuffer();
+
+ buf.append(Utils.indent(level) + "<modifier>\n");
+ buf.append(Utils.indent(level+1) +
+ "<type>" + Utils.xq(type) + "</type>\n");
+ if (value != null) {
+ buf.append(Utils.indent(level+1) + "<" + relationElement + ">" +
+ Utils.xq(comparison) + "</" + relationElement + ">\n");
+ buf.append(Utils.indent(level+1) +
+ "<value>" + Utils.xq(value) + "</value>\n");
+ }
+
+ buf.append(Utils.indent(level) + "</modifier>\n");
+ return buf.toString();
+ }
+
+ public String toCQL() {
+ StringBuffer buf = new StringBuffer(type);
+ if (value != null)
+ buf.append(" " + comparison + " " + value);
+
+ return buf.toString();
+ }
+}
--- /dev/null
+// $Id: ModifierSet.java,v 1.13 2007-07-03 13:30:18 mike Exp $
+
+package org.z3950.zing.cql;
+import java.util.Vector;
+import java.lang.StringBuffer;
+
+/**
+ * Represents a base String and a set of Modifiers.
+ * <P>
+ * This class is used as a workhorse delegate by both CQLRelation and
+ * CQLProxNode - two functionally very separate classes that happen to
+ * require similar data structures and functionality.
+ * <P>
+ * A ModifierSet consists of a ``base'' string together with a set of
+ * zero or more <I>type</I> <I>comparison</I> <I>value</I> pairs,
+ * where type, comparison and value are all strings.
+ *
+ * @version $Id: ModifierSet.java,v 1.13 2007-07-03 13:30:18 mike Exp $
+ */
+public class ModifierSet {
+ String base;
+ Vector<Modifier> modifiers;
+
+ /**
+ * Creates a new ModifierSet with the specified base.
+ */
+ public ModifierSet(String base) {
+ this.base = base;
+ modifiers = new Vector<Modifier>();
+ }
+
+ /**
+ * Returns the base string with which the ModifierSet was created.
+ */
+ public String getBase() {
+ return base;
+ }
+
+ /**
+ * Adds a modifier of the specified <TT>type</TT>,
+ * <TT>comparison</TT> and <TT>value</TT> to a ModifierSet.
+ */
+ public void addModifier(String type, String comparison, String value) {
+ Modifier modifier = new Modifier(type, comparison, value);
+ modifiers.add(modifier);
+ }
+
+ /**
+ * Adds a modifier of the specified <TT>type</TT>, but with no
+ * <TT>comparison</TT> and <TT>value</TT>, to a ModifierSet.
+ */
+ public void addModifier(String type) {
+ Modifier modifier = new Modifier(type);
+ modifiers.add(modifier);
+ }
+
+ /**
+ * Returns the value of the modifier in the specified ModifierSet
+ * that corresponds to the specified type.
+ */
+ public String modifier(String type) {
+ int n = modifiers.size();
+ for (int i = 0; i < n; i++) {
+ Modifier mod = modifiers.get(i);
+ if (mod.type.equals(type))
+ return mod.value;
+ }
+ return null;
+ }
+
+ /**
+ * Returns an array of the modifiers in a ModifierSet.
+ * @return
+ * An array of Modifiers.
+ */
+ public Vector<Modifier> getModifiers() {
+ return modifiers;
+ }
+
+ public String toXCQL(int level, String topLevelElement) {
+ return underlyingToXCQL(level, topLevelElement, "value");
+ }
+
+ public String sortKeyToXCQL(int level) {
+ return underlyingToXCQL(level, "key", "index");
+ }
+
+ private String underlyingToXCQL(int level, String topLevelElement,
+ String valueElement) {
+ StringBuffer buf = new StringBuffer();
+ buf.append(Utils.indent(level) + "<" + topLevelElement + ">\n");
+ buf.append(Utils.indent(level+1) +
+ "<" + valueElement + ">" + Utils.xq(base) +
+ "</" + valueElement + ">\n");
+ if (modifiers.size() > 0) {
+ buf.append(Utils.indent(level+1) + "<modifiers>\n");
+ for (int i = 0; i < modifiers.size(); i++) {
+ buf.append(modifiers.get(i).toXCQL(level+2, "comparison"));
+ }
+ buf.append(Utils.indent(level+1) + "</modifiers>\n");
+ }
+ buf.append(Utils.indent(level) + "</" + topLevelElement + ">\n");
+ return buf.toString();
+ }
+
+ public String toCQL() {
+ StringBuffer buf = new StringBuffer(base);
+ for (int i = 0; i < modifiers.size(); i++) {
+ buf.append("/" + modifiers.get(i).toCQL());
+ }
+
+ return buf.toString();
+ }
+
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ System.err.println("Usage: ModifierSet <base> [<type> <comparison> <name>]...");
+ System.exit(1);
+ }
+
+ ModifierSet res = new ModifierSet(args[0]);
+ for (int i = 1; i < args.length; i += 3) {
+ res.addModifier(args[i], args[i+1], args[i+2]);
+ }
+
+ System.out.println(res.toCQL());
+ }
+}
--- /dev/null
+// $Id: PQFTranslationException.java,v 1.1 2002-11-06 20:13:45 mike Exp $
+
+package org.z3950.zing.cql;
+import java.lang.Exception;
+
+
+/**
+ * Base class for exceptions occurring when translating parse trees to PQF.
+ *
+ * @version $Id: PQFTranslationException.java,v 1.1 2002-11-06 20:13:45 mike Exp $
+ */
+public class PQFTranslationException extends Exception {
+ PQFTranslationException(String s) {
+ super(s);
+ }
+}
--- /dev/null
+// $Id: UnknownIndexException.java,v 1.2 2007-06-27 22:44:40 mike Exp $
+
+package org.z3950.zing.cql;
+import java.lang.Exception;
+
+
+/**
+ * Exception indicating that an index was not recognised.
+ * At compilation time, we accept any syntactically valid index;
+ * but when rendering a tree out as PQF, we need to translate the
+ * indexes into sets of Type-1 query attributes. If we can't do
+ * that, because the PQF configuration doesn't know about a relation,
+ * we throw one of these babies.
+ *
+ * @version $Id: UnknownIndexException.java,v 1.2 2007-06-27 22:44:40 mike Exp $
+ */
+public class UnknownIndexException extends PQFTranslationException {
+ /**
+ * Creates a new <TT>UnknownIndexException</TT>.
+ * @param s
+ * The index for which there was no PQF configuration.
+ */
+ public UnknownIndexException(String s) {
+ super(s);
+ }
+}
--- /dev/null
+// $Id: UnknownPositionException.java,v 1.2 2002-11-29 16:42:54 mike Exp $
+
+package org.z3950.zing.cql;
+import java.lang.Exception;
+
+
+/**
+ * Exception indicating that a position was not recognised.
+ * When rendering a tree out as PQF, each term is classified either as
+ * <TT>any</TT>, <TT>first</TT>, <TT>last</TT> or
+ * <TT>firstAndLast</TT>, depending on whether it begins and/or ends
+ * with the word-anchoring meta-character <TT>^</TT>. Its
+ * classification is looked up as a <TT>position</TT> in the PQF
+ * configuration. If the position is not configured, we throw one of
+ * these babies.
+ *
+ * @version $Id: UnknownPositionException.java,v 1.2 2002-11-29 16:42:54 mike Exp $
+ */
+public class UnknownPositionException extends PQFTranslationException {
+ /**
+ * Creates a new <TT>UnknownPositionException</TT>.
+ * @param s
+ * The position for which there was no PQF configuration.
+ */
+ public UnknownPositionException(String s) {
+ super(s);
+ }
+}
--- /dev/null
+// $Id: UnknownRelationException.java,v 1.2 2002-11-06 20:13:45 mike Exp $
+
+package org.z3950.zing.cql;
+import java.lang.Exception;
+
+
+/**
+ * Exception indicating that a relation was not recognised.
+ * At compilation time, we accept any syntactically valid relation;
+ * but when rendering a tree out as PQF, we need to translate the
+ * relations into sets of Type-1 query attributes. If we can't do
+ * that, because the PQF configuration doesn't know about a relation,
+ * we throw one of these babies.
+ *
+ * @version $Id: UnknownRelationException.java,v 1.2 2002-11-06 20:13:45 mike Exp $
+ */
+public class UnknownRelationException extends PQFTranslationException {
+ /**
+ * Creates a new <TT>UnknownRelationException</TT>.
+ * @param s
+ * The relation for which there was no PQF configuration.
+ */
+ public UnknownRelationException(String s) {
+ super(s);
+ }
+}
--- /dev/null
+// $Id: UnknownRelationModifierException.java,v 1.1 2002-11-06 20:13:45 mike Exp $
+
+package org.z3950.zing.cql;
+import java.lang.Exception;
+
+
+/**
+ * Exception indicating that a relation modifier was not recognised.
+ * At compilation time, we accept any syntactically valid relation modifier;
+ * but when rendering a tree out as PQF, we need to translate the
+ * relation modifiers into sets of Type-1 query attributes. If we can't do
+ * that, because the PQF configuration doesn't know about a relation modifier,
+ * we throw one of these babies.
+ *
+ * @version $Id: UnknownRelationModifierException.java,v 1.1 2002-11-06 20:13:45 mike Exp $
+ */
+public class UnknownRelationModifierException extends PQFTranslationException {
+ /**
+ * Creates a new <TT>UnknownRelationModifierException</TT>.
+ * @param s
+ * The relation modifier for which there was no PQF configuration.
+ */
+ public UnknownRelationModifierException(String s) {
+ super(s);
+ }
+}
--- /dev/null
+// $Id: Utils.java,v 1.2 2002-11-06 00:05:58 mike Exp $
+
+package org.z3950.zing.cql;
+
+
+/**
+ * Utility functions for the org.z3950.zing.cql package.
+ * Not intended for use outside this package.
+ *
+ * @version $Id: Utils.java,v 1.2 2002-11-06 00:05:58 mike Exp $
+ */
+class Utils {
+ static String indent(int level) {
+ String x = "";
+ while (level-- > 0) {
+ x += " ";
+ }
+ return x;
+ }
+
+ // XML Quote --
+ // s/&/&/g;
+ // s/</</g;
+ // s/>/>/g;
+ // This is hideously inefficient, but I just don't see a better
+ // way using the standard JAVA library.
+ //
+ static String xq(String str) {
+ str = replaceString(str, "&", "&");
+ str = replaceString(str, "<", "<");
+ str = replaceString(str, ">", ">");
+ return str;
+ }
+
+ // I can't _believe_ I have to write this by hand in 2002 ...
+ static String replaceString(String str, String from, String to) {
+ StringBuffer sb = new StringBuffer();
+ int ix; // index of next `from'
+ int offset = 0; // index of previous `from' + length(from)
+
+ while ((ix = str.indexOf(from, offset)) != -1) {
+ sb.append(str.substring(offset, ix));
+ sb.append(to);
+ offset = ix + from.length();
+ }
+
+ // End of string: append last bit and we're done
+ sb.append(str.substring(offset));
+ return sb.toString();
+ }
+}
+++ /dev/null
-// $Id: CQLAndNode.java,v 1.9 2007-06-29 12:48:21 mike Exp $
-
-package org.z3950.zing.cql;
-
-
-/**
- * Represents an AND node in a CQL parse-tree.
- *
- * @version $Id: CQLAndNode.java,v 1.9 2007-06-29 12:48:21 mike Exp $
- */
-public class CQLAndNode extends CQLBooleanNode {
- /**
- * Creates a new AND node with the specified left- and right-hand
- * sides and modifiers.
- */
- public CQLAndNode(CQLNode left, CQLNode right, ModifierSet ms) {
- super(left, right, ms);
- }
-
- // ### Too much code duplication here with OR and NOT
- byte[] opType1() {
- byte[] op = new byte[5];
- putTag(CONTEXT, 46, CONSTRUCTED, op, 0); // Operator
- putLen(2, op, 2);
- putTag(CONTEXT, 0, PRIMITIVE, op, 3); // and
- putLen(0, op, 4);
- return op;
- }
-}
+++ /dev/null
-// $Id: CQLBooleanNode.java,v 1.18 2007-07-03 16:03:00 mike Exp $
-
-package org.z3950.zing.cql;
-import java.util.Properties;
-import java.util.Vector;
-
-
-/**
- * Represents a boolean node in a CQL parse-tree.
- *
- * @version $Id: CQLBooleanNode.java,v 1.18 2007-07-03 16:03:00 mike Exp $
- */
-public abstract class CQLBooleanNode extends CQLNode {
- /**
- * The root of a parse-tree representing the left-hand side.
- */
- public CQLNode left;
-
- /**
- * The root of a parse-tree representing the right-hand side.
- */
- public CQLNode right;
-
- /**
- * The set of modifiers that are applied to this boolean.
- */
- public ModifierSet ms;
-
- protected CQLBooleanNode(CQLNode left, CQLNode right, ModifierSet ms) {
- this.left = left;
- this.right = right;
- this.ms = ms;
- }
-
- public String toXCQL(int level, Vector<CQLPrefix> prefixes,
- Vector<ModifierSet> sortkeys) {
- return (indent(level) + "<triple>\n" +
- renderPrefixes(level+1, prefixes) +
- ms.toXCQL(level+1, "boolean") +
- indent(level+1) + "<leftOperand>\n" +
- left.toXCQL(level+2) +
- indent(level+1) + "</leftOperand>\n" +
- indent(level+1) + "<rightOperand>\n" +
- right.toXCQL(level+2) +
- indent(level+1) + "</rightOperand>\n" +
- renderSortKeys(level+1, sortkeys) +
- indent(level) + "</triple>\n");
- }
-
- public String toCQL() {
- // ### We don't always need parens around the operands
- return ("(" + left.toCQL() + ")" +
- " " + ms.toCQL() + " " +
- "(" + right.toCQL() + ")");
- }
-
- public String toPQF(Properties config) throws PQFTranslationException {
- return ("@" + opPQF() +
- " " + left.toPQF(config) +
- " " + right.toPQF(config));
- }
-
- // represents the operation for PQF: overridden for CQLProxNode
- String opPQF() { return ms.getBase(); }
-
- public byte[] toType1BER(Properties config) throws PQFTranslationException {
- System.out.println("in CQLBooleanNode.toType1BER(): PQF=" +
- toPQF(config));
- byte[] rpn1 = left.toType1BER(config);
- byte[] rpn2 = right.toType1BER(config);
- byte[] op = opType1();
- byte[] rpnStructure = new byte[rpn1.length+rpn2.length+op.length+4];
-
- // rpnRpnOp
- int offset = putTag(CONTEXT, 1, CONSTRUCTED, rpnStructure, 0);
-
- rpnStructure[offset++] = (byte)(0x80&0xff); // indefinite length
- System.arraycopy(rpn1, 0, rpnStructure, offset, rpn1.length);
- offset += rpn1.length;
- System.arraycopy(rpn2, 0, rpnStructure, offset, rpn2.length);
- offset += rpn2.length;
- System.arraycopy(op, 0, rpnStructure, offset, op.length);
- offset += op.length;
- rpnStructure[offset++] = 0x00; // end rpnRpnOp
- rpnStructure[offset++] = 0x00;
- return rpnStructure;
- }
-
- abstract byte[] opType1();
-}
+++ /dev/null
-// $Id: CQLGenerator.java,v 1.9 2007-07-03 15:41:35 mike Exp $
-
-package org.z3950.zing.cql;
-import java.util.Properties;
-import java.util.Random;
-import java.io.InputStream;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-
-
-/**
- * A generator that produces random CQL queries.
- * <P>
- * Why is that useful? Mainly to produce test-cases for CQL parsers
- * (including the <TT>CQLParser</TT> class in this package): you can
- * generate a random search tree, render it to XCQL and remember the
- * result. Then decompile the tree to CQL, feed the generated CQL to
- * the parser of your choice, and check that the XCQL it comes up with
- * is the same what you got from your initial rendering.
- * <P>
- * This code is based on the same grammar as the <TT>CQLParser</TT> class in
- * this distribution - there is a <TT>generate_<I>x</I>()</TT> method
- * for each grammar element <I>X</I>.
- *
- * @version $Id: CQLGenerator.java,v 1.9 2007-07-03 15:41:35 mike Exp $
- * @see <A href="http://zing.z3950.org/cql/index.html"
- * >http://zing.z3950.org/cql/index.html</A>
- */
-public class CQLGenerator {
- Properties params;
- Random rnd;
- static private boolean DEBUG = false;
-
- /**
- * Creates a new CQL generator with the specified parameters.
- * <P>
- * @param params
- * A <TT>Properties</TT> table containing configuration
- * parameters for the queries to be generated by this generator.
- * Recognised parameters are:
- * <P>
- * <DL>
- * <DT><TT>seed</TT></DT>
- * <DD>
- * If specified, this is a <TT>long</TT> used to seed the
- * random number generator, so that the CQL generator can be
- * run repeatably, giving the same results each time. If it's
- * omitted, then no seed is explicitly specified, and the
- * results of each run will be different (so long as you don't
- * run it more that 2^32 times :-)
- * <P>
- * </DD>
- * <DT><TT>complexQuery</TT></DT>
- * <DD>
- * [mandatory] A floating-point number between 0.0 and 1.0,
- * indicating the probability for each <TT>cql-query</TT> node
- * that it will be expanded into a ``complex query''
- * (<TT>cql-query boolean search-clause</TT>) rather
- * than a <TT>search-clause</TT>.
- * <P>
- * </DD>
- * <DT><TT>complexClause</TT></DT>
- * <DD>
- * [mandatory] A floating-point number between 0.0 and 1.0,
- * indicating the probability for each <TT>search-clause</TT>
- * node that it will be expanded into a full sub-query rather
- * than an <TT>[ index relation ] term</TT> triplet.
- * <P>
- * </DD>
- * <DT><TT>proxOp</TT></DT>
- * <DD>
- * [mandatory] A floating-point number between 0.0 and 1.0,
- * indicating the probability that each boolean operator will
- * be chosen to be proximity operation; otherwise, the three
- * simpler boolean operations (<TT>and</TT>, <TT>or</TT> and
- * <TT>not</TT>) are chosen with equal probability.
- * <P>
- * </DD>
- * <DT><TT>equalsRelation</TT></DT>
- * <DD>
- * [mandatory] A floating-point number between 0.0 and 1.0,
- * indicating the probability that each relation will be chosen
- * to be <TT>=</TT> - this is treated as a special case, since
- * it's likely to be by far the most common relation in
- * ``real life'' searches.
- * <P>
- * </DD>
- * <DT><TT>numericRelation</TT></DT>
- * <DD>
- * [mandatory] A floating-point number between 0.0 and 1.0,
- * indicating the probability that a relation, having chosen
- * not to be <TT>=</TT>, is instead chosen to be one of the six
- * numeric relations (<TT><</TT>, <TT>></TT>,
- * <TT><=</TT>, <TT>>=</TT>, <TT><></TT> and
- * <TT>=</TT>).
- * <P>
- * </DD>
- * </DL>
- */
- public CQLGenerator(Properties params) {
- this.params = params;
- String seed = params.getProperty("seed");
- if (seed != null)
- rnd = new Random(new Long(seed).longValue());
- else
- rnd = new Random();
- }
-
- private static void debug(String str) {
- if (DEBUG)
- System.err.println("DEBUG: " + str);
- }
-
- /**
- * Generates a single random CQL query.
- * <P>
- * Uses the parameters that were associated with the generator
- * when it was created. You are free to create as many random
- * queries as you wish from a single generator; each of them will
- * use the same parameters.
- * <P>
- * @return
- * A <TT>CQLNode</TT> that is the root of the generated tree.
- * That tree may be rendered in XCQL using its <TT>toXCQL()</TT>
- * method, or decompiled into CQL using its <TT>toCQL</TT>
- * method.
- */
- public CQLNode generate() throws MissingParameterException {
- return generate_cql_query();
- }
-
- private CQLNode generate_cql_query() throws MissingParameterException {
- if (!maybe("complexQuery")) {
- return generate_search_clause();
- }
-
- CQLNode node1 = generate_cql_query();
- CQLNode node2 = generate_search_clause();
- // ### should generate prefix-mapping nodes
- if (maybe("proxOp")) {
- // ### generate proximity nodes
- } else {
- switch (rnd.nextInt(3)) {
- case 0: return new CQLAndNode(node1, node2, new ModifierSet("and"));
- case 1: return new CQLOrNode (node1, node2, new ModifierSet("or"));
- case 2: return new CQLNotNode(node1, node2, new ModifierSet("not"));
- }
- }
-
- return generate_search_clause();
- }
-
- private CQLNode generate_search_clause() throws MissingParameterException {
- if (maybe("complexClause")) {
- return generate_cql_query();
- }
-
- // ### Should sometimes generate index/relation-free terms
- String index = generate_index();
- CQLRelation relation = generate_relation();
- String term = generate_term();
-
- return new CQLTermNode(index, relation, term);
- }
-
- // ### Should probably be more configurable
- private String generate_index() {
- String index = ""; // shut up compiler warning
- if (rnd.nextInt(2) == 0) {
- switch (rnd.nextInt(3)) {
- case 0: index = "dc.author"; break;
- case 1: index = "dc.title"; break;
- case 2: index = "dc.subject"; break;
- }
- } else {
- switch (rnd.nextInt(4)) {
- case 0: index = "bath.author"; break;
- case 1: index = "bath.title"; break;
- case 2: index = "bath.subject"; break;
- case 3: index = "foo>bar"; break;
- }
- }
-
- return index;
- }
-
- private CQLRelation generate_relation() throws MissingParameterException {
- String base = generate_base_relation();
- CQLRelation rel = new CQLRelation(base);
- // ### should generate modifiers too
- return rel;
- }
-
- private String generate_base_relation() throws MissingParameterException {
- if (maybe("equalsRelation")) {
- return "=";
- } else if (maybe("numericRelation")) {
- return generate_numeric_relation();
- } else {
- switch (rnd.nextInt(3)) {
- case 0: return "exact";
- case 1: return "all";
- case 2: return "any";
- }
- }
-
- // NOTREACHED
- return ""; // shut up compiler warning
- }
-
- // ### could read candidate terms from /usr/dict/words
- // ### should introduce wildcard characters
- // ### should generate multi-word terms
- private String generate_term() {
- switch (rnd.nextInt(10)) {
- case 0: return "cat";
- case 1: return "\"cat\"";
- case 2: return "comp.os.linux";
- case 3: return "xml:element";
- case 4: return "<xml.element>";
- case 5: return "prox/word/>=/5";
- case 6: return "";
- case 7: return "frog fish";
- case 8: return "the complete dinosaur";
- case 9: return "foo*bar";
- }
-
- // NOTREACHED
- return ""; // shut up compiler warning
- }
-
- private String generate_numeric_relation() {
- switch (rnd.nextInt(6)) {
- case 0: return "<";
- case 1: return ">";
- case 2: return "<=";
- case 3: return ">=";
- case 4: return "<>";
- case 5: return "=";
- }
-
- // NOTREACHED
- return ""; // shut up compiler warning
- }
-
- boolean maybe(String param) throws MissingParameterException {
- String probability = params.getProperty(param);
- if (probability == null)
- throw new MissingParameterException(param);
-
- double dice = rnd.nextDouble();
- double threshhold = new Double(probability).doubleValue();
- boolean res = dice < threshhold;
- debug("dice=" + String.valueOf(dice).substring(0, 8) +
- " vs. " + threshhold + "='" + param + "': " + res);
- return res;
- }
-
-
- /**
- * A simple test-harness for the generator.
- * <P>
- * It generates a single random query using the parameters
- * specified in a nominated properties file, plus any additional
- * <I>name value</I> pairs provided on the command-line, and
- * decompiles it into CQL which is written to standard output.
- * <P>
- * For example,
- * <TT>java org.z3950.zing.cql.CQLGenerator
- * etc/generate.properties seed 18398</TT>,
- * where the file <TT>generate.properties</TT> contains:<PRE>
- * complexQuery=0.4
- * complexClause=0.4
- * equalsRelation=0.5
- * numericRelation=0.7
- * proxOp=0.0
- * </PRE>
- * yields:<PRE>
- * ((dc.author = "<xml.element>") or (bath.title = cat)) and
- * (dc.subject >= "the complete dinosaur")
- * </PRE>
- * <P>
- * @param configFile
- * The name of a properties file from which to read the
- * configuration parameters (see above).
- * @param name
- * The name of a configuration parameter.
- * @param value
- * The value to assign to the configuration parameter named in
- * the immediately preceding command-line argument.
- * @return
- * A CQL query expressed in a form that should be comprehensible
- * to all conformant CQL compilers.
- */
- public static void main (String[] args) throws Exception {
- if (args.length % 2 != 1) {
- System.err.println("Usage: CQLGenerator <props-file> "+
- "[<name> <value>]...");
- System.exit(1);
- }
-
- String configFile = args[0];
- InputStream f = new FileInputStream(configFile);
- if (f == null)
- throw new FileNotFoundException(configFile);
-
- Properties params = new Properties();
- params.load(f);
- f.close();
- for (int i = 1; i < args.length; i += 2)
- params.setProperty(args[i], args[i+1]);
-
- CQLGenerator generator = new CQLGenerator(params);
- CQLNode tree = generator.generate();
- System.out.println(tree.toCQL());
- }
-}
+++ /dev/null
-// $Id: CQLLexer.java,v 1.14 2007-07-03 13:30:42 mike Exp $
-
-package org.z3950.zing.cql;
-import java.io.StreamTokenizer;
-import java.io.StringReader;
-import java.util.Hashtable;
-
-
-// This is a semi-trivial subclass for java.io.StreamTokenizer that:
-// * Has a halfDecentPushBack() method that actually works
-// * Includes a render() method
-// * Knows about the multi-character tokens "<=", ">=" and "<>"
-// * Recognises a set of keywords as tokens in their own right
-// * Includes some primitive debugging-output facilities
-// It's used only by CQLParser.
-//
-class CQLLexer extends StreamTokenizer {
- // New publicly visible token-types
- static int TT_LE = 1000; // The "<=" relation
- static int TT_GE = 1001; // The ">=" relation
- static int TT_NE = 1002; // The "<>" relation
- static int TT_EQEQ = 1003; // The "==" relation
- static int TT_AND = 1004; // The "and" boolean
- static int TT_OR = 1005; // The "or" boolean
- static int TT_NOT = 1006; // The "not" boolean
- static int TT_PROX = 1007; // The "prox" boolean
- static int TT_SORTBY = 1008; // The "sortby" operator
-
- // Support for keywords. It would be nice to compile this linear
- // list into a Hashtable, but it's hard to store ints as hash
- // values, and next to impossible to use them as hash keys. So
- // we'll just scan the (very short) list every time we need to do
- // a lookup.
- private class Keyword {
- int token;
- String keyword;
- Keyword(int token, String keyword) {
- this.token = token;
- this.keyword = keyword;
- }
- }
- // This should logically be static, but Java won't allow it :-P
- private Keyword[] keywords = {
- new Keyword(TT_AND, "and"),
- new Keyword(TT_OR, "or"),
- new Keyword(TT_NOT, "not"),
- new Keyword(TT_PROX, "prox"),
- new Keyword(TT_SORTBY, "sortby"),
- };
-
- // For halfDecentPushBack() and the code at the top of nextToken()
- private static int TT_UNDEFINED = -1000;
- private int saved_ttype = TT_UNDEFINED;
- private double saved_nval;
- private String saved_sval;
-
- // Controls debugging output
- private static boolean DEBUG;
-
- CQLLexer(String cql, boolean lexdebug) {
- super(new StringReader(cql));
- wordChars('!', '?'); // ASCII-dependency!
- wordChars('[', '`'); // ASCII-dependency!
- quoteChar('"');
- ordinaryChar('=');
- ordinaryChar('<');
- ordinaryChar('>');
- ordinaryChar('/');
- ordinaryChar('(');
- ordinaryChar(')');
- wordChars('\'', '\''); // prevent this from introducing strings
- parseNumbers();
- DEBUG = lexdebug;
- }
-
- private static void debug(String str) {
- if (DEBUG)
- System.err.println("LEXDEBUG: " + str);
- }
-
- // I don't honestly understand why we need this, but the
- // documentation for java.io.StreamTokenizer.pushBack() is pretty
- // vague about its semantics, and it seems to me that they could
- // be summed up as "it doesn't work". This version has the very
- // clear semantics "pretend I didn't call nextToken() just then".
- //
- private void halfDecentPushBack() {
- saved_ttype = ttype;
- saved_nval = nval;
- saved_sval = sval;
- }
-
- public int nextToken() throws java.io.IOException {
- if (saved_ttype != TT_UNDEFINED) {
- ttype = saved_ttype;
- nval = saved_nval;
- sval = saved_sval;
- saved_ttype = TT_UNDEFINED;
- debug("using saved ttype=" + ttype + ", " +
- "nval=" + nval + ", sval='" + sval + "'");
- return ttype;
- }
-
- underlyingNextToken();
- if (ttype == '<') {
- debug("token starts with '<' ...");
- underlyingNextToken();
- if (ttype == '=') {
- debug("token continues with '=' - it's '<='");
- ttype = TT_LE;
- } else if (ttype == '>') {
- debug("token continues with '>' - it's '<>'");
- ttype = TT_NE;
- } else {
- debug("next token is " + render() + " (pushed back)");
- halfDecentPushBack();
- ttype = '<';
- debug("AFTER: ttype is now " + ttype + " - " + render());
- }
- } else if (ttype == '>') {
- debug("token starts with '>' ...");
- underlyingNextToken();
- if (ttype == '=') {
- debug("token continues with '=' - it's '>='");
- ttype = TT_GE;
- } else {
- debug("next token is " + render() + " (pushed back)");
- halfDecentPushBack();
- ttype = '>';
- debug("AFTER: ttype is now " + ttype + " - " + render());
- }
- } else if (ttype == '=') {
- debug("token starts with '=' ...");
- underlyingNextToken();
- if (ttype == '=') {
- debug("token continues with '=' - it's '=='");
- ttype = TT_EQEQ;
- } else {
- debug("next token is " + render() + " (pushed back)");
- halfDecentPushBack();
- ttype = '=';
- debug("AFTER: ttype is now " + ttype + " - " + render());
- }
- }
-
- debug("done nextToken(): ttype=" + ttype + ", " +
- "nval=" + nval + ", " + "sval='" + sval + "'" +
- " (" + render() + ")");
-
- return ttype;
- }
-
- // It's important to do keyword recognition here at the lowest
- // level, otherwise when one of these words follows "<" or ">"
- // (which can be the beginning of multi-character tokens) it gets
- // pushed back as a string, and its keywordiness is not
- // recognised.
- //
- public int underlyingNextToken() throws java.io.IOException {
- super.nextToken();
- if (ttype == TT_WORD)
- for (int i = 0; i < keywords.length; i++)
- if (sval.equalsIgnoreCase(keywords[i].keyword))
- ttype = keywords[i].token;
-
- return ttype;
- }
-
- // Simpler interface for the usual case: current token with quoting
- String render() {
- return render(ttype, true);
- }
-
- String render(int token, boolean quoteChars) {
- if (token == TT_EOF) {
- return "EOF";
- } else if (token == TT_NUMBER) {
- if ((double) nval == (int) nval) {
- return new Integer((int) nval).toString();
- } else {
- return new Double((double) nval).toString();
- }
- } else if (token == TT_WORD) {
- return "word: " + sval;
- } else if (token == '"') {
- return "string: \"" + sval + "\"";
- } else if (token == TT_LE) {
- return "<=";
- } else if (token == TT_GE) {
- return ">=";
- } else if (token == TT_NE) {
- return "<>";
- } else if (token == TT_EQEQ) {
- return "==";
- }
-
- // Check whether its associated with one of the keywords
- for (int i = 0; i < keywords.length; i++)
- if (token == keywords[i].token)
- return keywords[i].keyword;
-
- // Otherwise it must be a single character, such as '(' or '/'.
- String res = String.valueOf((char) token);
- if (quoteChars) res = "'" + res + "'";
- return res;
- }
-
- public static void main(String[] args) throws Exception {
- if (args.length > 1) {
- System.err.println("Usage: CQLLexer [<CQL-query>]");
- System.err.println("If unspecified, query is read from stdin");
- System.exit(1);
- }
-
- String cql;
- if (args.length == 1) {
- cql = args[0];
- } else {
- byte[] bytes = new byte[10000];
- try {
- // Read in the whole of standard input in one go
- int nbytes = System.in.read(bytes);
- } catch (java.io.IOException ex) {
- System.err.println("Can't read query: " + ex.getMessage());
- System.exit(2);
- }
- cql = new String(bytes);
- }
-
- CQLLexer lexer = new CQLLexer(cql, true);
- int token;
- while ((token = lexer.nextToken()) != TT_EOF) {
- // Nothing to do: debug() statements render tokens for us
- }
- }
-}
+++ /dev/null
-// $Id: CQLNode.java,v 1.26 2007-07-03 13:36:03 mike Exp $
-
-package org.z3950.zing.cql;
-import java.util.Properties;
-import java.util.Vector;
-
-
-/**
- * Represents a node in a CQL parse-tree.
- *
- * @version $Id: CQLNode.java,v 1.26 2007-07-03 13:36:03 mike Exp $
- */
-public abstract class CQLNode {
- CQLNode() {} // prevent javadoc from documenting this
-
- /**
- * Returns the name of the result-set to which this query is a
- * reference, if and only if the entire query consists only of a
- * result-set reference. If it's anything else, including a
- * boolean combination of a result-set reference with something
- * else, then null is returned instead.
- * @return the name of the referenced result-set
- */
- public String getResultSetName() {
- return null;
- }
-
- /**
- * Translates a parse-tree into an XCQL document.
- * <P>
- * @param level
- * The number of levels to indent the top element of the XCQL
- * document. This will typically be 0 when invoked by an
- * application; it takes higher values when this method is
- * invoked recursively for nodes further down the tree.
- * @return
- * A String containing an XCQL document equivalent to the
- * parse-tree whose root is this node.
- */
- public String toXCQL(int level) {
- return toXCQL(level, null);
- }
-
- public String toXCQL(int level, Vector<CQLPrefix> prefixes) {
- return toXCQL(level, prefixes, null);
- }
-
- abstract public String toXCQL(int level, Vector<CQLPrefix> prefixes,
- Vector<ModifierSet> sortkeys);
-
- protected static String renderPrefixes(int level, Vector prefixes) {
- if (prefixes == null || prefixes.size() == 0)
- return "";
- String res = indent(level) + "<prefixes>\n";
- for (int i = 0; i < prefixes.size(); i++) {
- CQLPrefix p = (CQLPrefix) prefixes.get(i);
- res += indent(level+1) + "<prefix>\n";
- if (p.name != null)
- res += indent(level+2) + "<name>" + p.name + "</name>\n";
- res += indent(level+2) +
- "<identifier>" + p.identifier + "</identifier>\n";
- res += indent(level+1) + "</prefix>\n";
- }
- return res + indent(level) + "</prefixes>\n";
- }
-
- protected static String renderSortKeys(int level,
- Vector<ModifierSet> sortkeys) {
- if (sortkeys == null || sortkeys.size() == 0)
- return "";
- String res = indent(level) + "<sortKeys>\n";
- for (int i = 0; i < sortkeys.size(); i++) {
- ModifierSet key = sortkeys.get(i);
- res += key.sortKeyToXCQL(level+1);
- }
- return res + indent(level) + "</sortKeys>\n";
- }
-
- /**
- * Decompiles a parse-tree into a CQL query.
- * <P>
- * @return
- * A String containing a CQL query equivalent to the parse-tree
- * whose root is this node, so that compiling that query will
- * yield an identical tree.
- */
- abstract public String toCQL();
-
- /**
- * Renders a parse-tree into a Yaz-style PQF string.
- * PQF, or Prefix Query Format, is a cryptic but powerful notation
- * that can be trivially mapped, one-to-one, int Z39.50 Type-1 and
- * Type-101 queries. A specification for the format can be found
- * in
- * <A href="http://indexdata.dk/yaz/doc/tools.php#PQF"
- * >Chapter 7 (Supporting Tools)</A> of the
- * <A href="http://indexdata.dk/yaz/">YAZ</A> manual.
- * <P>
- * @param config
- * A <TT>Properties</TT> object containing configuration
- * information that specifies the mapping from CQL indexes,
- * relations, etc. to Type-1 attributes. The mapping
- * specification is described in the CQL-Java distribution's
- * sample PQF-mapping configuration file,
- * <TT>etc/pqf.properties</TT>, which see.
- * @return
- * A String containing a PQF query equivalent to the parse-tree
- * whose root is this node.
- */
- abstract public String toPQF(Properties config)
- throws PQFTranslationException;
-
- /**
- * Returns a String of spaces for indenting to the specified level.
- */
- protected static String indent(int level) { return Utils.indent(level); }
-
- /**
- * Returns the argument String quoted for XML.
- * For example, each occurrence of <TT><</TT> is translated to
- * <TT>&lt;</TT>.
- */
- protected static String xq(String str) { return Utils.xq(str); }
-
- /**
- * Renders a parser-tree into a BER-endoded packet representing an
- * equivalent Z39.50 Type-1 query. If you don't know what that
- * means, then you don't need this method :-) This is useful
- * primarily for SRW-to-Z39.50 gateways.
- *
- * @param config
- * A <TT>Properties</TT> object containing configuration
- * information that specifies the mapping from CQL indexes,
- * relations, etc. to Type-1 attributes. The mapping
- * specification is described in the CQL-Java distribution's
- * sample PQF-mapping configuration file,
- * <TT>etc/pqf.properties</TT>, which see.
- * @return
- * A byte array containing the BER packet.
- * @see
- * <A href="ftp://ftp.rsasecurity.com/pub/pkcs/ascii/layman.asc"
- * >ftp://ftp.rsasecurity.com/pub/pkcs/ascii/layman.asc</A>
- */
- abstract public byte[] toType1BER(Properties config)
- throws PQFTranslationException;
-
- // ANS.1 classes
- protected static final int UNIVERSAL = 0;
- protected static final int APPLICATION = 1;
- protected static final int CONTEXT = 2;
- protected static final int PRIVATE = 3;
-
- // ASN.1 tag forms
- protected static final int PRIMITIVE = 0;
- protected static final int CONSTRUCTED = 1;
-
- // ASN.1 UNIVERSAL data types
- public static final byte BOOLEAN = 1;
- public static final byte INTEGER = 2;
- public static final byte BITSTRING = 3;
- public static final byte OCTETSTRING = 4;
- public static final byte NULL = 5;
- public static final byte OBJECTIDENTIFIER = 6;
- public static final byte OBJECTDESCRIPTOR = 7;
- public static final byte EXTERNAL = 8;
- public static final byte ENUMERATED = 10;
- public static final byte SEQUENCE = 16;
- public static final byte SET = 17;
- public static final byte VISIBLESTRING = 26;
- public static final byte GENERALSTRING = 27;
-
- protected static final int putTag(int asn1class, int fldid, int form,
- byte[] record, int offset) {
- if (fldid < 31)
- record[offset++] = (byte)(fldid + asn1class*64 + form*32);
- else {
- record[offset++] = (byte)(31 + asn1class*64 + form*32);
- if (fldid < 128)
- record[offset++] = (byte)(fldid);
- else {
- record[offset++] = (byte)(128 + fldid/128);
- record[offset++] = (byte)(fldid % 128);
- }
- }
- return offset;
- }
-
- /**
- * Put a length directly into a BER record.
- *
- * @param len length to put into record
- * @return the new, incremented value of the offset parameter.
- */
- public // ### shouldn't this be protected?
- static final int putLen(int len, byte[] record, int offset) {
-
- if (len < 128)
- record[offset++] = (byte)len;
- else {
- int t;
- record[offset] = (byte)(lenLen(len) - 1);
- for (t = record[offset]; t > 0; t--) {
- record[offset+t] = (byte)(len & 0xff);
- len >>= 8;
- }
- t = offset;
- offset += (record[offset]&0xff) + 1;
- record[t] += 128; // turn on bit 8 in length byte.
- }
- return offset;
- }
-
- /**
- * Get the length needed to represent the given length.
- *
- * @param length determine length needed to encode this
- * @return length needed to encode given length
- */
- protected // ### shouldn't this be private?
- static final int lenLen(int length) {
-
- return ((length < 128) ? 1 :
- (length < 256) ? 2 :
- (length < 65536L) ? 3 : 4);
- }
-
- /**
- * Get the length needed to represent the given number.
- *
- * @param num determine length needed to encode this
- * @return length needed to encode given number
- */
- protected static final int numLen(long num) {
- num = num < 0 ? -num : num;
- // ### Wouldn't this be better done algorithmically?
- // Or at least with the constants expressed in hex?
- return ((num < 128) ? 1 :
- (num < 32768) ? 2 :
- (num < 8388608) ? 3 :
- (num < 2147483648L) ? 4 :
- (num < 549755813888L) ? 5 :
- (num < 140737488355328L) ? 6 :
- (num < 36028797018963968L) ? 7 : 8);
- }
-
- /**
- * Put a number into a given buffer
- *
- * @param num number to put into buffer
- * @param record buffer to use
- * @param offset offset into buffer
- * @return the new, incremented value of the offset parameter.
- */
- protected static final int putNum(long num, byte record[], int offset) {
- int cnt=numLen(num);
-
- for (int count = cnt - 1; count >= 0; count--) {
- record[offset+count] = (byte)(num & 0xff);
- num >>= 8;
- }
- return offset+cnt;
- }
-
- // Used only by the makeOID() method
- private static final java.util.Hashtable<String, byte[]> madeOIDs =
- new java.util.Hashtable<String, byte[]>(10);
-
- protected static final byte[] makeOID(String oid) {
- byte[] o;
- int dot, offset = 0, oidOffset = 0, value;
-
- if ((o = (byte[])madeOIDs.get(oid)) == null) {
- o = new byte[100];
-
- // Isn't this kind of thing excruciating in Java?
- while (oidOffset < oid.length() &&
- Character.isDigit(oid.charAt(oidOffset)) == true) {
- if (offset > 90) // too large
- return null;
-
- dot = oid.indexOf('.', oidOffset);
- if (dot == -1)
- dot = oid.length();
-
- value = Integer.parseInt(oid.substring(oidOffset, dot));
-
- if (offset == 0) { // 1st two are special
- if (dot == -1) // ### can't happen: -1 is reassigned above
- return null; // can't be this short
- oidOffset = dot+1; // skip past '.'
-
- dot = oid.indexOf('.', oidOffset);
- if (dot == -1)
- dot = oid.length();
-
- // ### Eh?!
- value = value * 40 +
- Integer.parseInt(oid.substring(oidOffset,dot));
- }
-
- if (value < 0x80) {
- o[offset++] = (byte)value;
- } else {
- int count = 0;
- byte bits[] = new byte[12]; // save a 84 (12*7) bit number
-
- while (value != 0) {
- bits[count++] = (byte)(value & 0x7f);
- value >>= 7;
- }
-
- // Now place in the correct order
- while (--count > 0)
- o[offset++] = (byte)(bits[count] | 0x80);
-
- o[offset++] = bits[count];
- }
-
- dot = oid.indexOf('.', oidOffset);
- if (dot == -1)
- break;
-
- oidOffset = dot+1;
- }
-
- byte[] ptr = new byte[offset];
- System.arraycopy(o, 0, ptr, 0, offset);
- madeOIDs.put(oid, ptr);
- return ptr;
- }
- return o;
- }
-
- public static final byte[] makeQuery(CQLNode root, Properties properties)
- throws PQFTranslationException {
- byte[] rpnStructure = root.toType1BER(properties);
- byte[] qry = new byte[rpnStructure.length+100];
- int offset = 0;
- offset = putTag(CONTEXT, 1, CONSTRUCTED, qry, offset);
- qry[offset++] = (byte)(0x80&0xff); // indefinite length
- offset = putTag(UNIVERSAL, OBJECTIDENTIFIER, PRIMITIVE, qry, offset);
- byte[] oid = makeOID("1.2.840.10003.3.1"); // bib-1
- offset = putLen(oid.length, qry, offset);
- System.arraycopy(oid, 0, qry, offset, oid.length);
- offset += oid.length;
- System.arraycopy(rpnStructure, 0, qry, offset, rpnStructure.length);
- offset += rpnStructure.length;
- qry[offset++] = 0x00; // end of query
- qry[offset++] = 0x00;
- byte[] q = new byte[offset];
- System.arraycopy(qry, 0, q, 0, offset);
- return q;
- }
-}
+++ /dev/null
-// $Id: CQLNotNode.java,v 1.9 2007-06-29 12:48:21 mike Exp $
-
-package org.z3950.zing.cql;
-
-
-/**
- * Represents a NOT node in a CQL parse-tree.
- *
- * @version $Id: CQLNotNode.java,v 1.9 2007-06-29 12:48:21 mike Exp $
- */
-public class CQLNotNode extends CQLBooleanNode {
- /**
- * Creates a new NOT node with the specified left- and right-hand
- * sides and modifiers.
- */
- public CQLNotNode(CQLNode left, CQLNode right, ModifierSet ms) {
- super(left, right, ms);
- }
-
- byte[] opType1() {
- byte[] op = new byte[5];
- putTag(CONTEXT, 46, CONSTRUCTED, op, 0); // Operator
- putLen(2, op, 2);
- putTag(CONTEXT, 2, PRIMITIVE, op, 3); // and-not
- putLen(0, op, 4);
- return op;
- }
-}
+++ /dev/null
-// $Id: CQLOrNode.java,v 1.9 2007-06-29 12:48:21 mike Exp $
-
-package org.z3950.zing.cql;
-
-
-/**
- * Represents an OR node in a CQL parse-tree.
- *
- * @version $Id: CQLOrNode.java,v 1.9 2007-06-29 12:48:21 mike Exp $
- */
-public class CQLOrNode extends CQLBooleanNode {
- /**
- * Creates a new OR node with the specified left- and right-hand
- * sides and modifiers.
- */
- public CQLOrNode(CQLNode left, CQLNode right, ModifierSet ms) {
- super(left, right, ms);
- }
-
- byte[] opType1() {
- byte[] op = new byte[5];
- putTag(CONTEXT, 46, CONSTRUCTED, op, 0); // Operator
- putLen(2, op, 2);
- putTag(CONTEXT, 1, PRIMITIVE, op, 3); // or
- putLen(0, op, 4);
- return op;
- }
-}
+++ /dev/null
-// $Id: CQLParseException.java,v 1.2 2002-11-06 20:13:45 mike Exp $
-
-package org.z3950.zing.cql;
-import java.lang.Exception;
-
-
-/**
- * Exception indicating that an error ocurred parsing CQL.
- *
- * @version $Id: CQLParseException.java,v 1.2 2002-11-06 20:13:45 mike Exp $
- */
-public class CQLParseException extends Exception {
- /**
- * Creates a new <TT>CQLParseException</TT>.
- * @param s
- * An error message describing the problem with the query,
- * usually a syntax error of some kind.
- */
- public CQLParseException(String s) {
- super(s);
- }
-}
-
+++ /dev/null
-// $Id: CQLParser.java,v 1.39 2007-08-06 15:54:48 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 CQL strings into parse trees of CQLNode subtypes.
- *
- * @version $Id: CQLParser.java,v 1.39 2007-08-06 15:54:48 mike Exp $
- * @see <A href="http://zing.z3950.org/cql/index.html"
- * >http://zing.z3950.org/cql/index.html</A>
- */
-public class CQLParser {
- private CQLLexer lexer;
- private int compat; // When false, implement CQL 1.2
- public static int V1POINT1 = 12368;
- public static int V1POINT2 = 12369;
- public static int V1POINT1SORT = 12370;
-
- static private boolean DEBUG = false;
- static private boolean LEXDEBUG = false;
-
- /**
- * The new parser implements a dialect of CQL specified by the
- * <tt>compat</tt> argument:
- * <ul>
- * <li>V1POINT1 - CQL version 1.1
- * </li>
- * <li>V1POINT2 - CQL version 1.2
- * </li>
- * <li>V1POINT1SORT - CQL version 1.1 but including
- * <tt>sortby</tt> as specified for CQL 1.2.
- * </li>
- * </ul>
- */
- public CQLParser(int compat) {
- this.compat = compat;
- }
-
- /**
- * The new parser implements CQL 1.2
- */
- public CQLParser() {
- this.compat = V1POINT2;
- }
-
- private static void debug(String str) {
- if (DEBUG)
- 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; <TT>toXCQL()</TT> returns an
- * XML snippet representing the query; and <TT>toPQF()</TT>
- * returns the query rendered in Index Data's Prefix Query
- * Format.
- *
- * @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);
-
- lexer.nextToken();
- debug("about to parseQuery()");
- CQLNode root = parseTopLevelPrefixes("cql.serverChoice",
- new CQLRelation(compat == V1POINT2 ? "=" : "scr"));
- if (lexer.ttype != lexer.TT_EOF)
- throw new CQLParseException("junk after end: " + lexer.render());
-
- return root;
- }
-
- private CQLNode parseTopLevelPrefixes(String index, CQLRelation relation)
- throws CQLParseException, IOException {
- debug("top-level prefix mapping");
-
- if (lexer.ttype == '>') {
- return parsePrefix(index, relation, true);
- }
-
- CQLNode node = parseQuery(index, relation);
- if ((compat == V1POINT2 || compat == V1POINT1SORT) &&
- lexer.ttype == lexer.TT_SORTBY) {
- match(lexer.ttype);
- debug("sortspec");
-
- CQLSortNode sortnode = new CQLSortNode(node);
- while (lexer.ttype != lexer.TT_EOF) {
- String sortindex = matchSymbol("sort index");
- ModifierSet ms = gatherModifiers(sortindex);
- sortnode.addSortIndex(ms);
- }
-
- if (sortnode.keys.size() == 0) {
- throw new CQLParseException("no sort keys");
- }
-
- node = sortnode;
- }
-
- return node;
- }
-
- private CQLNode parseQuery(String index, CQLRelation relation)
- throws CQLParseException, IOException {
- debug("in parseQuery()");
-
- CQLNode term = parseTerm(index, relation);
- while (lexer.ttype != lexer.TT_EOF &&
- lexer.ttype != ')' &&
- lexer.ttype != lexer.TT_SORTBY) {
- if (lexer.ttype == lexer.TT_AND ||
- lexer.ttype == lexer.TT_OR ||
- lexer.ttype == lexer.TT_NOT ||
- lexer.ttype == lexer.TT_PROX) {
- int type = lexer.ttype;
- String val = lexer.sval;
- match(type);
- ModifierSet ms = gatherModifiers(val);
- CQLNode term2 = parseTerm(index, relation);
- term = ((type == lexer.TT_AND) ? new CQLAndNode(term, term2, ms) :
- (type == lexer.TT_OR) ? new CQLOrNode (term, term2, ms) :
- (type == lexer.TT_NOT) ? new CQLNotNode(term, term2, ms) :
- new CQLProxNode(term, term2, ms));
- } else {
- throw new CQLParseException("expected boolean, got " +
- lexer.render());
- }
- }
-
- debug("no more ops");
- return term;
- }
-
- private ModifierSet gatherModifiers(String base)
- throws CQLParseException, IOException {
- debug("in gatherModifiers()");
-
- ModifierSet ms = new ModifierSet(base);
- while (lexer.ttype == '/') {
- match('/');
- if (lexer.ttype != lexer.TT_WORD)
- throw new CQLParseException("expected modifier, "
- + "got " + lexer.render());
- String type = lexer.sval.toLowerCase();
- match(lexer.ttype);
- if (!isRelation()) {
- // It's a simple modifier consisting of type only
- ms.addModifier(type);
- } else {
- // It's a complex modifier of the form type=value
- String comparision = lexer.render(lexer.ttype, false);
- match(lexer.ttype);
- String value = matchSymbol("modifier value");
- ms.addModifier(type, comparision, value);
- }
- }
-
- return ms;
- }
-
- private CQLNode parseTerm(String index, CQLRelation relation)
- throws CQLParseException, IOException {
- debug("in parseTerm()");
-
- String word;
- while (true) {
- if (lexer.ttype == '(') {
- debug("parenthesised term");
- match('(');
- CQLNode expr = parseQuery(index, relation);
- match(')');
- return expr;
- } else if (lexer.ttype == '>') {
- return parsePrefix(index, relation, false);
- }
-
- debug("non-parenthesised term");
- word = matchSymbol("index or term");
- if (!isRelation() && lexer.ttype != lexer.TT_WORD)
- break;
-
- index = word;
- String relstr = (lexer.ttype == lexer.TT_WORD ?
- lexer.sval : lexer.render(lexer.ttype, false));
- relation = new CQLRelation(relstr);
- match(lexer.ttype);
- ModifierSet ms = gatherModifiers(relstr);
- relation.setModifiers(ms);
- debug("index='" + index + ", " +
- "relation='" + relation.toCQL() + "'");
- }
-
- CQLTermNode node = new CQLTermNode(index, relation, word);
- debug("made term node " + node.toCQL());
- return node;
- }
-
- private CQLNode parsePrefix(String index, CQLRelation relation,
- boolean topLevel)
- throws CQLParseException, IOException {
- debug("prefix mapping");
-
- match('>');
- String name = null;
- String identifier = matchSymbol("prefix-name");
- if (lexer.ttype == '=') {
- match('=');
- name = identifier;
- identifier = matchSymbol("prefix-identifer");
- }
- CQLNode node = topLevel ?
- parseTopLevelPrefixes(index, relation) :
- parseQuery(index, relation);
-
- return new CQLPrefixNode(name, identifier, node);
- }
-
- // Checks for a relation
- private boolean isRelation() {
- debug("isRelation: checking ttype=" + lexer.ttype +
- " (" + lexer.render() + ")");
- return (lexer.ttype == '<' ||
- lexer.ttype == '>' ||
- lexer.ttype == '=' ||
- lexer.ttype == lexer.TT_LE ||
- lexer.ttype == lexer.TT_GE ||
- lexer.ttype == lexer.TT_NE ||
- lexer.ttype == lexer.TT_EQEQ);
- }
-
- private void match(int token)
- throws CQLParseException, IOException {
- debug("in match(" + lexer.render(token, true) + ")");
- if (lexer.ttype != token)
- throw new CQLParseException("expected " +
- lexer.render(token, true) +
- ", " + "got " + lexer.render());
- int tmp = lexer.nextToken();
- debug("match() got token=" + lexer.ttype + ", " +
- "nval=" + lexer.nval + ", sval='" + lexer.sval + "'" +
- " (tmp=" + tmp + ")");
- }
-
- private String matchSymbol(String expected)
- throws CQLParseException, IOException {
-
- debug("in matchSymbol()");
- if (lexer.ttype == lexer.TT_WORD ||
- lexer.ttype == lexer.TT_NUMBER ||
- lexer.ttype == '"' ||
- // The following is a complete list of keywords. Because
- // they're listed here, they can be used unquoted as
- // 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.
- lexer.ttype == lexer.TT_AND ||
- lexer.ttype == lexer.TT_OR ||
- lexer.ttype == lexer.TT_NOT ||
- lexer.ttype == lexer.TT_PROX ||
- lexer.ttype == lexer.TT_SORTBY) {
- String symbol = (lexer.ttype == lexer.TT_NUMBER) ?
- lexer.render() : lexer.sval;
- match(lexer.ttype);
- return symbol;
- }
-
- throw new CQLParseException("expected " + expected + ", " +
- "got " + lexer.render());
- }
-
-
- /**
- * 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>
- * <triple>
- * <boolean>
- * <value>and</value>
- * </boolean>
- * <triple>
- * <boolean>
- * <value>or</value>
- * </boolean>
- * <searchClause>
- * <index>au</index>
- * <relation>
- * <value>=</value>
- * </relation>
- * <term>Kerninghan</term>
- * </searchClause>
- * <searchClause>
- * <index>au</index>
- * <relation>
- * <value>=</value>
- * </relation>
- * <term>Ritchie</term>
- * </searchClause>
- * </triple>
- * <searchClause>
- * <index>ti</index>
- * <relation>
- * <value>=</value>
- * </relation>
- * <term>Unix</term>
- * </searchClause>
- * </triple>
- * </PRE>
- * <P>
- * @param -1
- * CQL version 1.1 (default version 1.2)
- * @param -d
- * Debug mode: extra output written to stderr.
- * @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) {
- char mode = 'x'; // x=XCQL, c=CQL, p=PQF
- String pfile = null;
-
- Vector<String> argv = new Vector<String>();
- for (int i = 0; i < args.length; i++) {
- argv.add(args[i]);
- }
-
- int compat = V1POINT2;
- if (argv.size() > 0 && argv.get(0).equals("-1")) {
- compat = V1POINT1;
- argv.remove(0);
- }
-
- if (argv.size() > 0 && argv.get(0).equals("-d")) {
- DEBUG = true;
- argv.remove(0);
- }
-
- 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 [-1] [-d] [-c] " +
- "[-p <pqf-properties> [<CQL-query>]");
- System.err.println("If unspecified, query is read from stdin");
- System.exit(1);
- }
-
- String cql;
- if (argv.size() == 1) {
- cql = (String) argv.get(0);
- } else {
- byte[] bytes = new byte[10000];
- try {
- // Read in the whole of standard input in one go
- int nbytes = System.in.read(bytes);
- } catch (IOException ex) {
- System.err.println("Can't read query: " + ex.getMessage());
- System.exit(2);
- }
- cql = new String(bytes);
- }
-
- CQLParser parser = new CQLParser(compat);
- CQLNode root = null;
- try {
- root = parser.parse(cql);
- } catch (CQLParseException ex) {
- System.err.println("Syntax error: " + ex.getMessage());
- System.exit(3);
- } catch (IOException ex) {
- 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 (IOException ex) {
- System.err.println("Can't render query: " + ex.getMessage());
- System.exit(5);
- } catch (UnknownIndexException ex) {
- System.err.println("Unknown index: " + ex.getMessage());
- System.exit(6);
- } catch (UnknownRelationException ex) {
- System.err.println("Unknown relation: " + ex.getMessage());
- System.exit(7);
- } catch (UnknownRelationModifierException ex) {
- System.err.println("Unknown relation modifier: " +
- ex.getMessage());
- System.exit(8);
- } catch (UnknownPositionException ex) {
- System.err.println("Unknown position: " + ex.getMessage());
- System.exit(9);
- } catch (PQFTranslationException ex) {
- // We catch all of this class's subclasses, so --
- throw new Error("can't get a PQFTranslationException");
- }
- }
-}
+++ /dev/null
-// $Id: CQLPrefix.java,v 1.5 2007-06-27 22:39:55 mike Exp $
-
-package org.z3950.zing.cql;
-import java.lang.String;
-
-/**
- * Represents a CQL prefix mapping from short name to long identifier.
- *
- * @version $Id: CQLPrefix.java,v 1.5 2007-06-27 22:39:55 mike Exp $
- */
-public class CQLPrefix {
- /**
- * The short name of the prefix mapping. That is, the prefix
- * itself, such as <TT>dc</TT>, as it might be used in an index
- * like <TT>dc.title</TT>.
- */
- public String name;
-
- /**
- * The full identifier name of the prefix mapping. That is,
- * typically, a URI permanently allocated to a specific index
- * set, such as <TT>http://zthes.z3950.org/cql/1.0<TT>.
- */
- public String identifier;
-
- /**
- * Creates a new CQLPrefix mapping, which maps the specified name
- * to the specified identifier.
- */
- CQLPrefix(String name, String identifier) {
- this.name = name;
- this.identifier = identifier;
- }
-}
+++ /dev/null
-// $Id: CQLPrefixNode.java,v 1.10 2007-07-03 16:40:11 mike Exp $
-
-package org.z3950.zing.cql;
-import java.lang.String;
-import java.util.Properties;
-import java.util.Vector;
-
-
-/**
- * Represents a prefix node in a CQL parse-tree.
- *
- * @version $Id: CQLPrefixNode.java,v 1.10 2007-07-03 16:40:11 mike Exp $
- */
-public class CQLPrefixNode extends CQLNode {
- /**
- * The prefix definition that governs the subtree.
- */
- public CQLPrefix prefix;
-
- /**
- * The root of a parse-tree representing the part of the query
- * that is governed by this prefix definition.
- */
- public CQLNode subtree;
-
- /**
- * Creates a new CQLPrefixNode inducing a mapping from the
- * specified index-set name to the specified identifier across
- * the specified subtree.
- */
- public CQLPrefixNode(String name, String identifier, CQLNode subtree) {
- this.prefix = new CQLPrefix(name, identifier);
- this.subtree = subtree;
- }
-
- public String toXCQL(int level, Vector<CQLPrefix> prefixes,
- Vector<ModifierSet> sortkeys) {
- Vector<CQLPrefix> tmp = (prefixes == null ?
- new Vector<CQLPrefix>() :
- new Vector<CQLPrefix>(prefixes));
- tmp.add(prefix);
- return subtree.toXCQL(level, tmp, sortkeys);
- }
-
- public String toCQL() {
- // ### We don't always need parens around the subtree
- if (prefix.name == null) {
- return ">\"" + prefix.identifier + "\" " +
- "(" + subtree.toCQL() + ")";
- } else {
- return ">" + prefix.name + "=\"" + prefix.identifier + "\" " +
- "(" + subtree.toCQL() + ")";
- }
- }
-
- public String toPQF(Properties config) throws PQFTranslationException {
- // Prefixes and their identifiers don't actually play any role
- // in PQF translation, since the meanings of the indexes,
- // including their prefixes if any, are instead wired into
- // `config'.
- return subtree.toPQF(config);
- }
-
- public byte[] toType1BER(Properties config) throws PQFTranslationException {
- // See comment on toPQF()
- return subtree.toType1BER(config);
- }
-}
+++ /dev/null
-// $Id: CQLProxNode.java,v 1.14 2007-06-29 12:53:03 mike Exp $
-
-package org.z3950.zing.cql;
-
-
-/**
- * Represents a proximity node in a CQL parse-tree.
- * The left- and right-hand-sides must be satisfied by parts of the
- * candidate records which are sufficiently close to each other, as
- * specified by a set of proximity parameters.
- *
- * @version $Id: CQLProxNode.java,v 1.14 2007-06-29 12:53:03 mike Exp $
- */
-public class CQLProxNode extends CQLBooleanNode {
- /**
- * Creates a new PROX node with the specified left- and right-hand
- * sides and modifiers.
- */
- public CQLProxNode(CQLNode left, CQLNode right, ModifierSet ms) {
- super(left, right, ms);
- }
-
- /*
- * proximity ::= exclusion distance ordered relation which-code unit-code.
- * exclusion ::= '1' | '0' | 'void'.
- * distance ::= integer.
- * ordered ::= '1' | '0'.
- * relation ::= integer.
- * which-code ::= 'known' | 'private' | integer.
- * unit-code ::= integer.
- */
- String opPQF() {
- int relCode = getRelCode();
- int unitCode = getProxUnitCode();
-
- String res = "prox " +
- "0 " +
- ms.modifier("distance") + " " +
- (ms.modifier("ordering").equals("ordered") ? 1 : 0) + " " +
- relCode + " " +
- "1 " +
- unitCode;
-
- return res;
- }
-
- private int getRelCode() {
- String rel = ms.modifier("relation");
- if (rel.equals("<")) {
- return 1;
- } else if (rel.equals("<=")) {
- return 2;
- } else if (rel.equals("=")) {
- return 3;
- } else if (rel.equals(">=")) {
- return 4;
- } else if (rel.equals(">")) {
- return 5;
- } else if (rel.equals("<>")) {
- return 6;
- }
- return 0;
- }
-
- private int getProxUnitCode() {
- String unit = ms.modifier("unit");
- if (unit.equals("word")) {
- return 2;
- } else if (unit.equals("sentence")) {
- return 3;
- } else if (unit.equals("paragraph")) {
- return 4;
- } else if (unit.equals("element")) {
- return 8;
- }
- return 0;
- }
-
-
- byte[] opType1() {
- byte[] op = new byte[100];
- int offset, value;
- offset = putTag(CONTEXT, 46, CONSTRUCTED, op, 0); // Operator
- op[offset++] = (byte)(0x80&0xff); // indefinite length
-
- offset = putTag(CONTEXT, 3, CONSTRUCTED, op, offset); // prox
- op[offset++] = (byte)(0x80&0xff); // indefinite length
-
- offset = putTag(CONTEXT, 1, PRIMITIVE, op, offset); // exclusion
- value = 0; // false
- offset = putLen(numLen(value), op, offset);
- offset = putNum(value, op, offset);
-
- offset = putTag(CONTEXT, 2, PRIMITIVE, op, offset); // distance
- value = Integer.parseInt(ms.modifier("distance"));
- offset = putLen(numLen(value), op, offset);
- offset = putNum(value, op, offset);
-
- offset = putTag(CONTEXT, 3, PRIMITIVE, op, offset); // ordered
- value = ms.modifier("ordering").equals("ordered") ? 1 : 0;
- offset = putLen(numLen(value), op, offset);
- offset = putNum(value, op, offset);
-
- offset = putTag(CONTEXT, 4, PRIMITIVE, op, offset); // relationType
- value = getRelCode();
- offset = putLen(numLen(value), op, offset);
- offset = putNum(value, op, offset);
-
- offset = putTag(CONTEXT, 5, CONSTRUCTED, op, offset); // proximityUnitCode
- op[offset++] = (byte)(0x80&0xff); // indefinite length
- offset = putTag(CONTEXT, 1, PRIMITIVE, op, offset); // known
- value = getProxUnitCode();
- offset = putLen(numLen(value), op, offset);
- offset = putNum(value, op, offset);
- op[offset++] = 0x00; // end of proximityUnitCode
- op[offset++] = 0x00;
-
- op[offset++] = 0x00; // end of prox
- op[offset++] = 0x00;
- op[offset++] = 0x00; // end of Operator
- op[offset++] = 0x00;
-
- byte[] o = new byte[offset];
- System.arraycopy(op, 0, o, 0, offset);
- return o;
- }
-}
+++ /dev/null
-// $Id: CQLRelation.java,v 1.19 2007-07-03 13:40:58 mike Exp $
-
-package org.z3950.zing.cql;
-import java.util.Vector;
-import java.util.Properties;
-import java.lang.StringBuffer;
-
-/**
- * Represents a relation between a CQL index and term.
- *
- * @version $Id: CQLRelation.java,v 1.19 2007-07-03 13:40:58 mike Exp $
- */
-public class CQLRelation extends CQLNode {
- ModifierSet ms;
-
- /**
- * Creates a new CQLRelation with the specified base relation.
- * Typical base relations include the usual six ordering relations
- * (<TT><=</TT>, <TT>></TT>, <I>etc.</I>), the text
- * relations <TT>any</TT>, <TT>all</TT> and <TT>exact</TT>, the
- * old server-choice relation <TT>scr</TT> and profiled relations of
- * the form <TT><I>prefix</I>.<I>name</I></TT>.
- */
- // ### Seems wrong: a modifier set should not have a base, a
- // relation should
- public CQLRelation(String base) {
- ms = new ModifierSet(base);
- }
-
- /**
- * Returns the base relation with which the CQLRelation was
- * originally created.
- */
- public String getBase() {
- return ms.getBase();
- }
-
- /**
- * Sets the modifiers of the specified CQLRelation.
- * Typical relation modifiers include <TT>relevant</TT>,
- * <TT>fuzzy</TT>, <TT>stem</TT> and <TT>phonetic</TT>. On the
- * whole, these modifiers have a meaningful interpretation only
- * for the text relations.
- */
- public void setModifiers(ModifierSet ms) {
- this.ms = ms;
- }
-
- /**
- * Returns an array of the modifiers associated with a CQLRelation.
- * @return
- * An array of Modifier objects.
- */
- public Vector<Modifier> getModifiers() {
- return ms.getModifiers();
- }
-
- public String toXCQL(int level, Vector prefixes, Vector sortkeys) {
- if (sortkeys != null)
- throw new Error("CQLRelation.toXCQL() called with sortkeys");
-
- return ms.toXCQL(level, "relation");
- }
-
- public String toCQL() {
- return ms.toCQL();
- }
-
- public String toPQF(Properties config) throws PQFTranslationException {
- throw new Error("CQLRelation.toPQF() can never be called");
- }
-
- public byte[] toType1BER(Properties config) {
- throw new Error("CQLRelation.toType1BER() can never be called");
- }
-}
+++ /dev/null
-// $Id: CQLSortNode.java,v 1.2 2008-04-11 12:05:15 mike Exp $
-
-package org.z3950.zing.cql;
-import java.util.Properties;
-import java.util.Vector;
-
-
-/**
- * Represents a sort node in a CQL parse-tree.
- *
- * @version $Id: CQLSortNode.java,v 1.2 2008-04-11 12:05:15 mike Exp $
- */
-public class CQLSortNode extends CQLNode {
- /**
- * The root of a subtree representing the query whose result is to
- * be sorted.
- */
- public CQLNode subtree;
-
- /**
- * The set of sort keys by which results are to be sorted,
- * each expressed as an index together with zero or more
- * modifiers.
- */
- Vector<ModifierSet> keys;
-
- public CQLSortNode(CQLNode subtree) {
- this.subtree = subtree;
- keys = new Vector<ModifierSet>();
- }
-
- public void addSortIndex(ModifierSet key) {
- keys.add(key);
- }
-
- public Vector<ModifierSet> getSortIndexes() {
- return keys;
- }
-
- public String toXCQL(int level, Vector<CQLPrefix> prefixes,
- Vector<ModifierSet> sortkeys) {
- if (sortkeys != null)
- throw new Error("CQLSortNode.toXCQL() called with sortkeys");
- return subtree.toXCQL(level, prefixes, keys);
- }
-
- public String toCQL() {
- StringBuffer buf = new StringBuffer(subtree.toCQL());
-
- if (keys != null) {
- buf.append(" sortby");
- for (int i = 0; i < keys.size(); i++) {
- ModifierSet key = keys.get(i);
- buf.append(" " + key.toCQL());
- }
- }
-
- return buf.toString();
- }
-
- public String toPQF(Properties config) throws PQFTranslationException {
- return "@attr 1=oops \"###\"";
- }
-
- public byte[] toType1BER(Properties config)
- throws PQFTranslationException {
- // There is no way to represent sorting in a standard Z39.50
- // Type-1 query, so the best we can do is return the
- // underlying query and ignore the sort-specification.
- return subtree.toType1BER(config);
- }
-}
+++ /dev/null
-// $Id: CQLTermNode.java,v 1.28 2007-07-03 13:41:24 mike Exp $
-
-package org.z3950.zing.cql;
-import java.util.Properties;
-import java.util.Vector;
-
-
-/**
- * Represents a terminal node in a CQL parse-tree.
- * A term node consists of the term String itself, together with,
- * optionally, an index string and a relation. Neither or both of
- * these must be provided - you can't have an index without a
- * relation or vice versa.
- *
- * @version $Id: CQLTermNode.java,v 1.28 2007-07-03 13:41:24 mike Exp $
- */
-public class CQLTermNode extends CQLNode {
- private String index;
- private CQLRelation relation;
- private String term;
-
- /**
- * Creates a new term node with the specified <TT>index</TT>,
- * <TT>relation</TT> and <TT>term</TT>. The first two may be
- * <TT>null</TT>, but the <TT>term</TT> may not.
- */
- public CQLTermNode(String index, CQLRelation relation, String term) {
- this.index = index;
- this.relation = relation;
- this.term = term;
- }
-
- public String getIndex() { return index; }
- public CQLRelation getRelation() { return relation; }
- public String getTerm() { return term; }
-
- private static boolean isResultSetIndex(String qual) {
- return (qual.equals("srw.resultSet") ||
- qual.equals("srw.resultSetId") ||
- qual.equals("srw.resultSetName") ||
- qual.equals("cql.resultSet") ||
- qual.equals("cql.resultSetId") ||
- qual.equals("cql.resultSetName"));
- }
-
- public String getResultSetName() {
- if (isResultSetIndex(index))
- return term;
- else
- return null;
- }
-
- public String toXCQL(int level, Vector<CQLPrefix> prefixes,
- Vector<ModifierSet> sortkeys) {
- return (indent(level) + "<searchClause>\n" +
- renderPrefixes(level+1, prefixes) +
- indent(level+1) + "<index>" + xq(index) + "</index>\n" +
- relation.toXCQL(level+1) +
- indent(level+1) + "<term>" + xq(term) + "</term>\n" +
- renderSortKeys(level+1, sortkeys) +
- indent(level) + "</searchClause>\n");
- }
-
- public String toCQL() {
- String quotedIndex = maybeQuote(index);
- String quotedTerm = maybeQuote(term);
- String res = quotedTerm;
-
- if (index != null &&
- !index.equalsIgnoreCase("srw.serverChoice") &&
- !index.equalsIgnoreCase("cql.serverChoice")) {
- // ### We don't always need spaces around `relation'.
- res = quotedIndex + " " + relation.toCQL() + " " + quotedTerm;
- }
-
- return res;
- }
-
- // ### Interaction between this and its callers is not good as
- // regards truncation of the term and generation of truncation
- // attributes. Change the interface to fix this.
- private Vector getAttrs(Properties config) throws PQFTranslationException {
- Vector<String> attrs = new Vector<String>();
-
- // Do this first so that if any other truncation or
- // completeness attributes are generated, they "overwrite"
- // those specified here.
- //
- // ### This approach relies on an unpleasant detail of Index
- // Data's (admittedly definitive) implementation of PQF,
- // and should not relied upon.
- //
- String attr = config.getProperty("always");
- if (attr != null)
- attrs.add(attr);
-
- attr = config.getProperty("index." + index);
- if (attr == null)
- throw new UnknownIndexException(index);
- attrs.add(attr);
-
- String rel = relation.getBase();
- if (rel.equals("=")) {
- rel = "eq";
- } else if (rel.equals("<=")) {
- rel = "le";
- } else if (rel.equals(">=")) {
- rel = "ge";
- }
- // ### Handling "any" and "all" properly would involve breaking
- // the string down into a bunch of individual words and ORring
- // or ANDing them together. Another day.
- attr = config.getProperty("relation." + rel);
- if (attr == null)
- throw new UnknownRelationException(rel);
- attrs.add(attr);
-
- Vector<Modifier> mods = relation.getModifiers();
- for (int i = 0; i < mods.size(); i++) {
- String type = mods.get(i).type;
- attr = config.getProperty("relationModifier." + type);
- if (attr == null)
- throw new UnknownRelationModifierException(type);
- attrs.add(attr);
- }
-
- String pos = "any";
- String text = term;
- if (text.length() > 0 && text.substring(0, 1).equals("^")) {
- text = text.substring(1); // ### change not seen by caller
- pos = "first";
- }
- int len = text.length();
- if (len > 0 && text.substring(len-1, len).equals("^")) {
- text = text.substring(0, len-1); // ### change not seen by caller
- pos = pos.equals("first") ? "firstAndLast" : "last";
- // ### in the firstAndLast case, the standard
- // pqf.properties file specifies that we generate a
- // completeness=whole-field attributem, which means that
- // we don't generate a position attribute at all. Do we
- // care? Does it matter?
- }
-
- attr = config.getProperty("position." + pos);
- if (attr == null)
- throw new UnknownPositionException(pos);
- attrs.add(attr);
-
- attr = config.getProperty("structure." + rel);
- if (attr == null)
- attr = config.getProperty("structure.*");
- attrs.add(attr);
-
- return attrs;
- }
-
- public String toPQF(Properties config) throws PQFTranslationException {
- if (isResultSetIndex(index)) {
- // Special case: ignore relation, modifiers, wildcards, etc.
- // There's parallel code in toType1BER()
- return "@set " + maybeQuote(term);
- }
-
- Vector attrs = getAttrs(config);
-
- String attr, s = "";
- for (int i = 0; i < attrs.size(); i++) {
- attr = (String) attrs.get(i);
- s += "@attr " + Utils.replaceString(attr, " ", " @attr ") + " ";
- }
-
- String text = term;
- if (text.length() > 0 && text.substring(0, 1).equals("^"))
- text = text.substring(1);
- int len = text.length();
- if (len > 0 && text.substring(len-1, len).equals("^"))
- text = text.substring(0, len-1);
-
- return s + maybeQuote(text);
- }
-
- static String maybeQuote(String str) {
- if (str == null)
- return null;
-
- // There _must_ be a better way to make this test ...
- if (str.length() == 0 ||
- str.indexOf('"') != -1 ||
- str.indexOf(' ') != -1 ||
- str.indexOf('\t') != -1 ||
- str.indexOf('=') != -1 ||
- str.indexOf('<') != -1 ||
- str.indexOf('>') != -1 ||
- str.indexOf('/') != -1 ||
- str.indexOf('(') != -1 ||
- str.indexOf(')') != -1) {
- str = '"' + Utils.replaceString(str, "\"", "\\\"") + '"';
- }
-
- return str;
- }
-
- public byte[] toType1BER(Properties config) throws PQFTranslationException {
- if (isResultSetIndex(index)) {
- // Special case: ignore relation, modifiers, wildcards, etc.
- // There's parallel code in toPQF()
- byte[] operand = new byte[term.length()+100];
- int offset;
- offset = putTag(CONTEXT, 0, CONSTRUCTED, operand, 0); // op
- operand[offset++] = (byte)(0x80&0xff); // indefinite length
- offset = putTag(CONTEXT, 31, PRIMITIVE, operand, offset); // ResultSetId
- byte[] t = term.getBytes();
- offset = putLen(t.length, operand, offset);
- System.arraycopy(t, 0, operand, offset, t.length);
- offset += t.length;
- operand[offset++] = 0x00; // end of Operand
- operand[offset++] = 0x00;
- byte[] o = new byte[offset];
- System.arraycopy(operand, 0, o, 0, offset);
- return o;
- }
-
- String text = term;
- if (text.length() > 0 && text.substring(0, 1).equals("^"))
- text = text.substring(1);
- int len = text.length();
- if (len > 0 && text.substring(len-1, len).equals("^"))
- text = text.substring(0, len-1);
-
- String attr, attrList, term = text;
- byte[] operand = new byte[text.length()+100];
- int i, j, offset, type, value;
- offset = putTag(CONTEXT, 0, CONSTRUCTED, operand, 0); // op
- operand[offset++]=(byte)(0x80&0xff); // indefinite length
- offset = putTag(CONTEXT, 102, CONSTRUCTED, operand, offset); // AttributesPlusTerm
- operand[offset++] = (byte)(0x80&0xff); // indefinite length
- offset = putTag(CONTEXT, 44, CONSTRUCTED, operand, offset); // AttributeList
- operand[offset++] = (byte)(0x80&0xff); // indefinite length
-
- Vector attrs = getAttrs(config);
- for(i = 0; i < attrs.size(); i++) {
- attrList = (String) attrs.get(i);
- java.util.StringTokenizer st =
- new java.util.StringTokenizer(attrList);
- while (st.hasMoreTokens()) {
- attr = st.nextToken();
- j = attr.indexOf('=');
- offset = putTag(UNIVERSAL, SEQUENCE, CONSTRUCTED, operand, offset);
- operand[offset++] = (byte)(0x80&0xff);
- offset = putTag(CONTEXT, 120, PRIMITIVE, operand, offset);
- type = Integer.parseInt(attr.substring(0, j));
- offset = putLen(numLen(type), operand, offset);
- offset = putNum(type, operand, offset);
-
- offset = putTag(CONTEXT, 121, PRIMITIVE, operand, offset);
- value = Integer.parseInt(attr.substring(j+1));
- offset = putLen(numLen(value), operand, offset);
- offset = putNum(value, operand, offset);
- operand[offset++] = 0x00; // end of SEQUENCE
- operand[offset++] = 0x00;
- }
- }
- operand[offset++] = 0x00; // end of AttributeList
- operand[offset++] = 0x00;
-
- offset = putTag(CONTEXT, 45, PRIMITIVE, operand, offset); // general Term
- byte[] t = term.getBytes();
- offset = putLen(t.length, operand, offset);
- System.arraycopy(t, 0, operand, offset, t.length);
- offset += t.length;
-
- operand[offset++] = 0x00; // end of AttributesPlusTerm
- operand[offset++] = 0x00;
- operand[offset++] = 0x00; // end of Operand
- operand[offset++] = 0x00;
- byte[] o = new byte[offset];
- System.arraycopy(operand, 0, o, 0, offset);
- return o;
- }
-}
+++ /dev/null
-# $Id: Makefile,v 1.18 2007-07-03 12:56:29 mike Exp $
-#
-# Your Java compiler will require that this source directory is on the
-# classpath. The best way to do that is just to add the CQL-Java
-# distribution's "src" subdirectory to your CLASSPATH environment
-# variable, like this:
-# CLASSPATH=$CLASSPATH:/where/ever/you/unpacked/it/cql-java-VERSION/src
-
-OBJ = Utils.class \
- CQLNode.class CQLTermNode.class CQLBooleanNode.class \
- CQLAndNode.class CQLOrNode.class CQLNotNode.class \
- CQLProxNode.class CQLPrefixNode.class CQLSortNode.class \
- CQLPrefix.class \
- CQLRelation.class Modifier.class ModifierSet.class \
- CQLParser.class CQLLexer.class CQLGenerator.class \
- CQLParseException.class MissingParameterException.class \
- PQFTranslationException.class \
- UnknownIndexException.class UnknownRelationException.class \
- UnknownRelationModifierException.class UnknownPositionException.class
-
-JARPATH = ../lib/cql-java.jar
-JAR = ../../../../$(JARPATH)
-$(JAR): $(OBJ)
- cd ../../../..; jar cf $(JARPATH) org/z3950/zing/cql/*.class
-
-%.class: %.java
- javac -Xlint:unchecked *.java
-
-test: $(JAR)
- cd ../../../../../test/regression && make
-
-clean:
- rm -f $(OBJ) 'CQLLexer$$Keyword.class'
-
+++ /dev/null
-// $Id: MissingParameterException.java,v 1.2 2002-11-06 20:13:45 mike Exp $
-
-package org.z3950.zing.cql;
-import java.lang.Exception;
-
-
-/**
- * Exception indicating that a required property was not specified.
- *
- * @version $Id: MissingParameterException.java,v 1.2 2002-11-06 20:13:45 mike Exp $
- */
-public class MissingParameterException extends Exception {
- /**
- * Creates a new <TT>MissingParameterException</TT>.
- * @param s
- * The name of the property whose value was required but not supplied.
- */
- public MissingParameterException(String s) {
- super(s);
- }
-}
+++ /dev/null
-// $Id: Modifier.java,v 1.4 2007-07-03 13:29:34 mike Exp $
-
-package org.z3950.zing.cql;
-import java.util.Vector;
-import java.lang.StringBuffer;
-
-/**
- * Represents a single modifier, consisting of three elements: a type,
- * a comparision and a value. For example, "distance", "<", "3". The
- * type is mandatory; either the comparison and value must both occur,
- * or neither must.
- * <P>
- * This class is used only by ModifierSet.
- *
- * @version $Id: Modifier.java,v 1.4 2007-07-03 13:29:34 mike Exp $
- */
-public class Modifier {
- String type;
- String comparison;
- String value;
-
- /**
- * Creates a new Modifier with the specified type, comparison
- * and value.
- */
- public Modifier(String type, String comparison, String value) {
- this.type = type;
- this.comparison = comparison;
- this.value = value;
- //System.err.println("Made new modifier with " + "type='" + type + "', " + "comparison='" + comparison + "', " + "value='" + value + "',\n");
- }
-
- /**
- * Creates a new Modifier with the specified type but no
- * comparison or value.
- */
- public Modifier(String type) {
- this.type = type;
- //System.err.println("Made new modifier of type '" + type + "'\n");
- }
-
- /**
- * Returns the type with which the Modifier was created.
- */
- public String getType() {
- return type;
- }
-
- /**
- * Returns the comparison with which the Modifier was created.
- */
- public String getComparison() {
- return comparison;
- }
-
- /**
- * Returns the value with which the Modifier was created.
- */
- public String getValue() {
- return value;
- }
-
- public String toXCQL(int level, String relationElement) {
- StringBuffer buf = new StringBuffer();
-
- buf.append(Utils.indent(level) + "<modifier>\n");
- buf.append(Utils.indent(level+1) +
- "<type>" + Utils.xq(type) + "</type>\n");
- if (value != null) {
- buf.append(Utils.indent(level+1) + "<" + relationElement + ">" +
- Utils.xq(comparison) + "</" + relationElement + ">\n");
- buf.append(Utils.indent(level+1) +
- "<value>" + Utils.xq(value) + "</value>\n");
- }
-
- buf.append(Utils.indent(level) + "</modifier>\n");
- return buf.toString();
- }
-
- public String toCQL() {
- StringBuffer buf = new StringBuffer(type);
- if (value != null)
- buf.append(" " + comparison + " " + value);
-
- return buf.toString();
- }
-}
+++ /dev/null
-// $Id: ModifierSet.java,v 1.13 2007-07-03 13:30:18 mike Exp $
-
-package org.z3950.zing.cql;
-import java.util.Vector;
-import java.lang.StringBuffer;
-
-/**
- * Represents a base String and a set of Modifiers.
- * <P>
- * This class is used as a workhorse delegate by both CQLRelation and
- * CQLProxNode - two functionally very separate classes that happen to
- * require similar data structures and functionality.
- * <P>
- * A ModifierSet consists of a ``base'' string together with a set of
- * zero or more <I>type</I> <I>comparison</I> <I>value</I> pairs,
- * where type, comparison and value are all strings.
- *
- * @version $Id: ModifierSet.java,v 1.13 2007-07-03 13:30:18 mike Exp $
- */
-public class ModifierSet {
- String base;
- Vector<Modifier> modifiers;
-
- /**
- * Creates a new ModifierSet with the specified base.
- */
- public ModifierSet(String base) {
- this.base = base;
- modifiers = new Vector<Modifier>();
- }
-
- /**
- * Returns the base string with which the ModifierSet was created.
- */
- public String getBase() {
- return base;
- }
-
- /**
- * Adds a modifier of the specified <TT>type</TT>,
- * <TT>comparison</TT> and <TT>value</TT> to a ModifierSet.
- */
- public void addModifier(String type, String comparison, String value) {
- Modifier modifier = new Modifier(type, comparison, value);
- modifiers.add(modifier);
- }
-
- /**
- * Adds a modifier of the specified <TT>type</TT>, but with no
- * <TT>comparison</TT> and <TT>value</TT>, to a ModifierSet.
- */
- public void addModifier(String type) {
- Modifier modifier = new Modifier(type);
- modifiers.add(modifier);
- }
-
- /**
- * Returns the value of the modifier in the specified ModifierSet
- * that corresponds to the specified type.
- */
- public String modifier(String type) {
- int n = modifiers.size();
- for (int i = 0; i < n; i++) {
- Modifier mod = modifiers.get(i);
- if (mod.type.equals(type))
- return mod.value;
- }
- return null;
- }
-
- /**
- * Returns an array of the modifiers in a ModifierSet.
- * @return
- * An array of Modifiers.
- */
- public Vector<Modifier> getModifiers() {
- return modifiers;
- }
-
- public String toXCQL(int level, String topLevelElement) {
- return underlyingToXCQL(level, topLevelElement, "value");
- }
-
- public String sortKeyToXCQL(int level) {
- return underlyingToXCQL(level, "key", "index");
- }
-
- private String underlyingToXCQL(int level, String topLevelElement,
- String valueElement) {
- StringBuffer buf = new StringBuffer();
- buf.append(Utils.indent(level) + "<" + topLevelElement + ">\n");
- buf.append(Utils.indent(level+1) +
- "<" + valueElement + ">" + Utils.xq(base) +
- "</" + valueElement + ">\n");
- if (modifiers.size() > 0) {
- buf.append(Utils.indent(level+1) + "<modifiers>\n");
- for (int i = 0; i < modifiers.size(); i++) {
- buf.append(modifiers.get(i).toXCQL(level+2, "comparison"));
- }
- buf.append(Utils.indent(level+1) + "</modifiers>\n");
- }
- buf.append(Utils.indent(level) + "</" + topLevelElement + ">\n");
- return buf.toString();
- }
-
- public String toCQL() {
- StringBuffer buf = new StringBuffer(base);
- for (int i = 0; i < modifiers.size(); i++) {
- buf.append("/" + modifiers.get(i).toCQL());
- }
-
- return buf.toString();
- }
-
- public static void main(String[] args) {
- if (args.length < 1) {
- System.err.println("Usage: ModifierSet <base> [<type> <comparison> <name>]...");
- System.exit(1);
- }
-
- ModifierSet res = new ModifierSet(args[0]);
- for (int i = 1; i < args.length; i += 3) {
- res.addModifier(args[i], args[i+1], args[i+2]);
- }
-
- System.out.println(res.toCQL());
- }
-}
+++ /dev/null
-// $Id: PQFTranslationException.java,v 1.1 2002-11-06 20:13:45 mike Exp $
-
-package org.z3950.zing.cql;
-import java.lang.Exception;
-
-
-/**
- * Base class for exceptions occurring when translating parse trees to PQF.
- *
- * @version $Id: PQFTranslationException.java,v 1.1 2002-11-06 20:13:45 mike Exp $
- */
-public class PQFTranslationException extends Exception {
- PQFTranslationException(String s) {
- super(s);
- }
-}
+++ /dev/null
-// $Id: UnknownIndexException.java,v 1.2 2007-06-27 22:44:40 mike Exp $
-
-package org.z3950.zing.cql;
-import java.lang.Exception;
-
-
-/**
- * Exception indicating that an index was not recognised.
- * At compilation time, we accept any syntactically valid index;
- * but when rendering a tree out as PQF, we need to translate the
- * indexes into sets of Type-1 query attributes. If we can't do
- * that, because the PQF configuration doesn't know about a relation,
- * we throw one of these babies.
- *
- * @version $Id: UnknownIndexException.java,v 1.2 2007-06-27 22:44:40 mike Exp $
- */
-public class UnknownIndexException extends PQFTranslationException {
- /**
- * Creates a new <TT>UnknownIndexException</TT>.
- * @param s
- * The index for which there was no PQF configuration.
- */
- public UnknownIndexException(String s) {
- super(s);
- }
-}
+++ /dev/null
-// $Id: UnknownPositionException.java,v 1.2 2002-11-29 16:42:54 mike Exp $
-
-package org.z3950.zing.cql;
-import java.lang.Exception;
-
-
-/**
- * Exception indicating that a position was not recognised.
- * When rendering a tree out as PQF, each term is classified either as
- * <TT>any</TT>, <TT>first</TT>, <TT>last</TT> or
- * <TT>firstAndLast</TT>, depending on whether it begins and/or ends
- * with the word-anchoring meta-character <TT>^</TT>. Its
- * classification is looked up as a <TT>position</TT> in the PQF
- * configuration. If the position is not configured, we throw one of
- * these babies.
- *
- * @version $Id: UnknownPositionException.java,v 1.2 2002-11-29 16:42:54 mike Exp $
- */
-public class UnknownPositionException extends PQFTranslationException {
- /**
- * Creates a new <TT>UnknownPositionException</TT>.
- * @param s
- * The position for which there was no PQF configuration.
- */
- public UnknownPositionException(String s) {
- super(s);
- }
-}
+++ /dev/null
-// $Id: UnknownRelationException.java,v 1.2 2002-11-06 20:13:45 mike Exp $
-
-package org.z3950.zing.cql;
-import java.lang.Exception;
-
-
-/**
- * Exception indicating that a relation was not recognised.
- * At compilation time, we accept any syntactically valid relation;
- * but when rendering a tree out as PQF, we need to translate the
- * relations into sets of Type-1 query attributes. If we can't do
- * that, because the PQF configuration doesn't know about a relation,
- * we throw one of these babies.
- *
- * @version $Id: UnknownRelationException.java,v 1.2 2002-11-06 20:13:45 mike Exp $
- */
-public class UnknownRelationException extends PQFTranslationException {
- /**
- * Creates a new <TT>UnknownRelationException</TT>.
- * @param s
- * The relation for which there was no PQF configuration.
- */
- public UnknownRelationException(String s) {
- super(s);
- }
-}
+++ /dev/null
-// $Id: UnknownRelationModifierException.java,v 1.1 2002-11-06 20:13:45 mike Exp $
-
-package org.z3950.zing.cql;
-import java.lang.Exception;
-
-
-/**
- * Exception indicating that a relation modifier was not recognised.
- * At compilation time, we accept any syntactically valid relation modifier;
- * but when rendering a tree out as PQF, we need to translate the
- * relation modifiers into sets of Type-1 query attributes. If we can't do
- * that, because the PQF configuration doesn't know about a relation modifier,
- * we throw one of these babies.
- *
- * @version $Id: UnknownRelationModifierException.java,v 1.1 2002-11-06 20:13:45 mike Exp $
- */
-public class UnknownRelationModifierException extends PQFTranslationException {
- /**
- * Creates a new <TT>UnknownRelationModifierException</TT>.
- * @param s
- * The relation modifier for which there was no PQF configuration.
- */
- public UnknownRelationModifierException(String s) {
- super(s);
- }
-}
+++ /dev/null
-// $Id: Utils.java,v 1.2 2002-11-06 00:05:58 mike Exp $
-
-package org.z3950.zing.cql;
-
-
-/**
- * Utility functions for the org.z3950.zing.cql package.
- * Not intended for use outside this package.
- *
- * @version $Id: Utils.java,v 1.2 2002-11-06 00:05:58 mike Exp $
- */
-class Utils {
- static String indent(int level) {
- String x = "";
- while (level-- > 0) {
- x += " ";
- }
- return x;
- }
-
- // XML Quote --
- // s/&/&/g;
- // s/</</g;
- // s/>/>/g;
- // This is hideously inefficient, but I just don't see a better
- // way using the standard JAVA library.
- //
- static String xq(String str) {
- str = replaceString(str, "&", "&");
- str = replaceString(str, "<", "<");
- str = replaceString(str, ">", ">");
- return str;
- }
-
- // I can't _believe_ I have to write this by hand in 2002 ...
- static String replaceString(String str, String from, String to) {
- StringBuffer sb = new StringBuffer();
- int ix; // index of next `from'
- int offset = 0; // index of previous `from' + length(from)
-
- while ((ix = str.indexOf(from, offset)) != -1) {
- sb.append(str.substring(offset, ix));
- sb.append(to);
- offset = ix + from.length();
- }
-
- // End of string: append last bit and we're done
- sb.append(str.substring(offset));
- return sb.toString();
- }
-}