diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..dc3a3a1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org +root = true + +[*.java] +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true + +[*] +trim_trailing_whitespace = true diff --git a/.github/workflows/mvn-verify.yaml b/.github/workflows/mvn-verify.yaml new file mode 100644 index 0000000..ffc7a46 --- /dev/null +++ b/.github/workflows/mvn-verify.yaml @@ -0,0 +1,16 @@ +name: mvn-verify +on: [push, pull_request] + +jobs: + mvn-verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Set up JDK 11 + uses: actions/setup-java@v5 + with: + java-version: '11' + distribution: 'temurin' + cache: maven + - name: Run the Maven verify phase + run: mvn --batch-mode verify diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..76c3508 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +target/ +*.swp +*~ +build +dist +nbproject diff --git a/Build b/Build deleted file mode 100755 index a399888..0000000 --- a/Build +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -# $Id: Build,v 1.2 2002-11-20 17:20:11 mike Exp $ - -: ${MAKE=make} -for dir in src/org/z3950/zing/cql test/regression docs; do - echo "=== Building in $dir ===" - ( cd $dir; $MAKE ${@+"$@"} ) -done diff --git a/Changes b/Changes index 4441afe..1503a26 100644 --- a/Changes +++ b/Changes @@ -1,4 +1,3 @@ -$Id: Changes,v 1.46 2008-05-28 10:03:53 mike Exp $ Revision history for the CQL-Java package. See the bottom of this file for a list of things still to do. @@ -6,6 +5,72 @@ See the bottom of this file for a list of things still to do. The work for releases 1.0, 1.2 and 1.2.1 was sponsored by the National Library of Australia, whose help we gratefully acknowledge. +1.13 Wed 10 May 2017 14:44:44 CET + - add classes for visitor traversal + - truncation attribute right/left/both/none (thanks to Rustam Usmanov) + - exact relation == (thanks to Rustam Usmanov) + - CQLNodeVisitor rewritten to allow for generation of prefix, + postfix and infix notations + - enum CQLBoolean + +1.12 Mon 17 Mar 2014 16:16:16 CET + - rewrite the CQL lexer to better conform to CQL spec + (e.g retain escapes for wildcards, etc) + - add jUnit test cases + - align list of terms (non-compliant) handling with Yaz CQL parser + +1.11 Tue 5 Mar 2013 13:38:18 CET + - Fix problem with parsing unquoted terms that begin with a '.' + - Fix problem with parsing unquoted terms that contain a '@' + - allow registration of custom relations + - optional flag to set strict-but-not-compliant mode that prohibits + unqouted keywords to be used as terms + +1.8 Fri Jul 22 18:02:41 CEST 2011 + - API changes: removed direct access to internal state and introduced + getters, removed usages of obsolete collections (Vector, Hashtable), + simplified method signatues (toXCQL), modified constants to be + read-only + - implemented faster XCQL serialization, using pure StringBuilder + +1.7 Tue Jul 27 17:35:25 BST 2010 + - Patch from Ralph LeVan to recognise + "qualifier.dc.creator"-like properties as well as + "index.dc.creator" -- backwards compatible with version + prior to 1.0 as well as those subsequent. + - Reinstate the "exact" relation even when v1.2 is in effect + (the default), despite its removal from v1.2 of CQL. In + practice, it seems to be in fairly widespread use, and + little real purpose is served in removing it. + +1.6 Thu Jun 17 10:56:24 BST 2010 + - Fix the bug where "9x" is lexed as two tokens, a TT_NUMBER + followed by a TT_WORD, making unquoted search-terms such as + 2010-06-17 syntax errors. Thanks to Erik Hetzner + for the patch. + +1.5 Tue May 25 16:44:35 CEST 2010 + - Fix recognition of word-relations to correctly match what is + specified in the CQL v1.1 and v1.2 documents. In + particular, "encloses" and "within" are recognised, so that + Masterkey's use of "encloses" for IP range authentication + works again. + - Update regression tests accordingly. + - Extend test/random/ to emit summary line of successful + recompilations. + +1.4 Thu May 20 00:38:40 BST 2010 + - Add support for interpreting sequences of non-keywords as a + single multi-word term rather than a broken INDEX RELATION + TERM triplet. This means that "oxford street" is a valid + query rather than a syntax error, and "tottenham court road" + is what it looks like rather than a search for the word + "court" related by the relation "court" to the index + "tottenham". Note that the word-formed relations "any", + "all", "exact" and (for CQL v1.2) "scr" are still treated + specially, as are profiled relations in context sets, of the + form .. + 1.3 Wed May 28 11:02:00 BST 2008 - Add getSortIndexes() to CQLSortNode, as suggested by Marcel Versteeg @@ -174,10 +239,6 @@ Library of Australia, whose help we gratefully acknowledge. case-sensitivity. - Add a new configuration file that allows arbitrary new relations and relation modifiers to be defined. - - Fix the bug where "9x" is parsed as two tokens, a TT_NUMBER - followed by a TT_WORD. The problem here is that I don't - think it's actually possible to fix this without throwing - out StreamTokenizer and rolling our own, which we absolutely - Fix term-to-PQF translation to omit empty properties (for the broken Korean server) - Write "package.html" file for the javadoc documentation. diff --git a/Makefile b/Makefile deleted file mode 100644 index 2836ab6..0000000 --- a/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -# $Id: Makefile,v 1.1 2002-11-17 23:29:55 mike Exp $ - -**make-default**: - ./Build - -clean: - ./Build clean - diff --git a/README b/README.md similarity index 60% rename from README rename to README.md index eea8b25..df16dca 100644 --- a/README +++ b/README.md @@ -1,10 +1,7 @@ -$Id: README,v 1.24 2007-06-07 16:04:50 mike Exp $ +# CQL-Java -- a free CQL parser and related tools, for Java -CQL-Java - a free CQL compiler, and other CQL tools, for Java - -INTRODUCTION ------------- +## Introduction CQL-Java is a Free Software project that provides: @@ -22,39 +19,44 @@ CQL-Java is a Free Software project that provides: CQL is "Common Query Language", a query language designed under the umbrella of the ZING initiative (Z39.59-International Next Generation). The official specification is at - http://www.loc.gov/standards/sru/cql/ + https://www.loc.gov/standards/sru/cql/ and there's more (and friendlier) information at - http://zing.z3950.org/cql/index.html + https://zing.z3950.org/cql/index.html XCQL is "XML CQL", a representation of CQL-equivalent queries in XML which is supposed to be easier to parse. The specification is at - http://www.loc.gov/standards/sru/xml-files/xcql.xsd + https://docs.oasis-open.org/search-ws/searchRetrieve/v1.0/os/schemas/xcql.xsd in the form of an XML Schema. But if you didn't know that, why are you even reading this? :-) -WHAT'S WHAT IN THIS DISTRIBUTION? ---------------------------------- +## What's What in this Distribution? + +* `README.md` -- This file +* [`Changes`](Changes) -- History of releases +* [`LGPL-2.1`](LGPL-2.1) -- The GNU lesser GPL (see below) +* `pom.xml` -- Maven project file to control compilation. +* `src` -- Source-code for the CQL-Java library and tests +* `target` -- The compiled library file, `cql-java.jar` and javadoc +* `bin` -- Simple shell-scripts to invoke CQL programs (parser/lexer/generator) +* `util` -- Various testing and sanity-checking Perl scripts +* `etc` -- Other files: PQF indexes, generator properties, etc. + + +## Compilation and Installation - README This file - VERSION The version-number of this distribution - Changes History of releases - LGPL-2.1 The GNU lesser GPL (see below) - Makefile, Build Files to control compilation. - src Source-code for the CQL-Java library - lib The compiled library file, "cql-java.jar" - bin Simple shell-scripts to invoke the test-harnesses - docs Documentation automatically generated by "javadoc" - test Various testing and sanity-checking frameworks - etc Other files: PQF indexes, generator properties, etc. +The build process is controlled by Maven so compilation is the standard: + + mvn clean install + +which generates build artifacts under `target/`. "Installation" of this package would consist of putting the bin -directory on your PATH and lib/cql-java.jar on your CLASSPATH. +directory on your PATH and `target/cql-java.jar` on your CLASSPATH. -SYNOPSIS --------- +## Synopsis Using the test-harnesses: @@ -86,29 +88,28 @@ Using the library in your own applications: // ... where `config' specifies CQL-qualfier => Z-attr mapping -DESCRIPTION ------------ +## Description -See the automatically generated class documentation in the "doc" +See the automatically generated class documentation in the `target` subdirectory. -AUTHOR ------- +## Author + +Original code and documentation by Mike Taylor, Index Data +At present maintained by Jakub Skoczen, Index Data + +* https://www.indexdata.com/cql-java -Code and documentation by Mike Taylor, Index Data - http://indexdata.com - http://zing.z3950.org/cql -Please email me with bug-reports, wishlist items, patches, deployment +Please email us with bug-reports, wishlist items, patches, deployment stories and, of course, large cash donations. -LICENCE -------- +## Licence The CQL-Java suite is Free Software, which is pretty much legally equivalent -- though not morally equivalent -- to Open Source. See - http://www.gnu.org/philosophy/free-software-for-freedom.html + https://www.gnu.org/philosophy/free-software-for-freedom.html for a detailed if somewhat one-sided discussion of the differences, and particularly of why Free Software is an important idea. @@ -121,10 +122,11 @@ much anything else with it. In particular, you may deploy CQL-Java as a part of a non-free larger work. -SEE ALSO --------- +## See also -Adam Dickmeiss's CQL compiler, written in C. -Rob Sanderson's CQL compiler, written in Python. -All the other free CQL compilers everyone's going to write :-) -The "Changes" file, including the "Still to do" section. +* https://zing.z3950.org/cql +* Adam Dickmeiss's CQL compiler, written in C. +* Rob Sanderson's CQL compiler, written in Python. +* Jakub Skoczen's CQL-js compiler, written in JavaScript https://github.com/indexdata/cql-js +* All the other free CQL compilers everyone's going to write :-) +* The [`Changes`](Changes) file, including the "Still to do" section. diff --git a/VERSION b/VERSION deleted file mode 100644 index 7e32cd5..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.3 diff --git a/archive/chris-hubick/README b/archive/chris-hubick/README index b7292e5..35021e4 100644 --- a/archive/chris-hubick/README +++ b/archive/chris-hubick/README @@ -1,4 +1,3 @@ -$Id: README,v 1.1 2005-08-29 15:15:02 mike Exp $ This directory contains a substantial rewrite of CQL-Java v0.7 as supplied by Chris Hubick on 22nd August 2005. I don't think diff --git a/bin/CQLGenerator b/bin/CQLGenerator index 97c2765..a1c3a2f 100755 --- a/bin/CQLGenerator +++ b/bin/CQLGenerator @@ -1,6 +1,7 @@ -#!/bin/sh +#!/bin/bash -# $Id: CQLGenerator,v 1.1 2002-11-02 01:24:41 mike Exp $ # Trivial script to invoke the CQLGenerator test-harness +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +JAR=$DIR/../target/cql-java.jar -java org.z3950.zing.cql.CQLGenerator ${@+"$@"} +java -cp $JAR org.z3950.zing.cql.CQLGenerator ${@+"$@"} diff --git a/bin/CQLLexer b/bin/CQLLexer index 4967738..6e94eb3 100755 --- a/bin/CQLLexer +++ b/bin/CQLLexer @@ -1,6 +1,7 @@ -#!/bin/sh +#!/bin/bash -# $Id: CQLLexer,v 1.2 2002-11-01 23:45:28 mike Exp $ # Trivial script to invoke the CQLLexer test-harness +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +JAR=$DIR/../target/cql-java.jar -java org.z3950.zing.cql.CQLLexer ${@+"$@"} +java -cp $JAR org.z3950.zing.cql.CQLLexer ${@+"$@"} diff --git a/bin/CQLParser b/bin/CQLParser index 3147d85..d03e5cf 100755 --- a/bin/CQLParser +++ b/bin/CQLParser @@ -1,6 +1,7 @@ -#!/bin/sh +#!/bin/bash -# $Id: CQLParser,v 1.2 2002-11-01 23:45:28 mike Exp $ # Trivial script to invoke the CQLParser test-harness +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +JAR=$DIR/../target/cql-java.jar -java org.z3950.zing.cql.CQLParser ${@+"$@"} +java -cp $JAR org.z3950.zing.cql.CQLParser ${@+"$@"} diff --git a/docs/.cvsignore b/docs/.cvsignore deleted file mode 100644 index c796f0b..0000000 --- a/docs/.cvsignore +++ /dev/null @@ -1,14 +0,0 @@ -allclasses-frame.html -allclasses-noframe.html -constant-values.html -deprecated-list.html -help-doc.html -index-all.html -index.html -org -overview-tree.html -package-list -packages.html -serialized-form.html -stylesheet.css -resources diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index c77810a..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# $Id: Makefile,v 1.2 2007-06-06 13:20:44 mike Exp $ - -all: overview-tree.html - -overview-tree.html: ../src/org/z3950/zing/cql/*.java - nice javadoc -classpath ../src \ - -author -version -windowtitle cql-java org.z3950.zing.cql - -clean: - rm -rf allclasses-frame.html \ - allclasses-noframe.html \ - deprecated-list.html \ - help-doc.html \ - index-all.html \ - index.html \ - org \ - overview-tree.html \ - package-list \ - packages.html \ - serialized-form.html \ - constant-values.html \ - stylesheet.css \ - resources diff --git a/etc/pqf.properties b/etc/pqf.properties index 6de0172..36a0483 100644 --- a/etc/pqf.properties +++ b/etc/pqf.properties @@ -1,4 +1,3 @@ -# $Id: pqf.properties,v 1.9 2003-12-18 13:17:16 mike Exp $ # # Propeties file to drive org.z3950.zing.cql.CQLNode's toPQF() # back-end. This specifies the interpretation of various CQL @@ -97,6 +96,17 @@ structure.any = 4=2 structure.* = 4=1 # phrase +# Truncation attributes used to implement CQL wildcard patterns. The +# simpler forms, left, right- and both-truncation will be used for the +# simplest patterns, so that we produce PQF queries that conform more +# closely to the Bath Profile. However, when a more complex pattern +# such as "foo*bar" is used, we fall back on Z39.58-style masking. +# +truncation.right = 5=1 +truncation.left = 5=2 +truncation.both = 5=3 +truncation.none = 5=100 + # Finally, any additional attributes that should always be included # with each term can be specified in the "always" property. # diff --git a/etc/xslt/README b/etc/xslt/README index b6918eb..0ee649f 100644 --- a/etc/xslt/README +++ b/etc/xslt/README @@ -1,4 +1,3 @@ -$Id: README,v 1.2 2007-06-06 14:38:25 mike Exp $ The XSLT files in this directory are supposed to allow you to make a thin-client for SRU using only an XSLT-enabled web browser. It diff --git a/lib/.cvsignore b/lib/.cvsignore deleted file mode 100644 index 9950dc8..0000000 --- a/lib/.cvsignore +++ /dev/null @@ -1 +0,0 @@ -cql-java.jar diff --git a/lib/README b/lib/README deleted file mode 100644 index 3bc0875..0000000 --- a/lib/README +++ /dev/null @@ -1,5 +0,0 @@ -$Id: README,v 1.2 2002-12-09 16:29:44 mike Exp $ - -The library file "cql-java.jar" will appear here when you do a build -in ../src/org/z3950/zing/cql. Put it on your CLASSPATH to use the -CQL-Java utilities. diff --git a/mkdist b/mkdist deleted file mode 100755 index e5d401e..0000000 --- a/mkdist +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -# mkdist - make a distribution of the CQL-Java software -# $Id: mkdist,v 1.6 2008-05-28 10:06:40 mike Exp $ - -version=`cat VERSION` -distdir=cql-java-$version -distfile=$distdir.tar.gz -tmpdir=/tmp/$distdir -echo "Building $distdir" -trap "rm -rf $tmpdir" 1 2 15 # 0 -cp -rp . $tmpdir -cd $tmpdir -find . \( -name CVS -o -name .cvsignore \) -print | xargs rm -rf -rm -r archive mkdist etc/chris-hubick etc/xslt src/org/z3950/zing/ralph -( cd src/org/z3950/zing/cql && make && make clean ) -( cd docs && make ) -( cd test/regression && make distclean ) -cd .. -tar cf - $distdir | gzip -9v > $distfile -rm -rf $tmpdir -echo "Made $distfile" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9357503 --- /dev/null +++ b/pom.xml @@ -0,0 +1,191 @@ + + 4.0.0 + org.z3950.zing + cql-java + jar + 1.14-SNAPSHOT + cql-java + https://www.indexdata.com/cql-java + + + LGPL-2.1-only + https://spdx.org/licenses/LGPL-2.1-only.html + repo + + + 2008 + + Index Data + https://www.indexdata.com + + + + Mike Taylor + mike + mike@indexdata.com + Index Data + https://www.indexdata.com + + + + + Jakub Skoczen + jakub + jakub@indexdata.dk + Index Data + https://www.indexdata.com + + + + + + scm:git:git://github.com/indexdata/cql-java.git + https://github.com/indexdata/cql-java + HEAD + + + GitHub + https://github.com/indexdata/cql-java/issues + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + UTF-8 + ${javadoc.opts} + + + + + + cql-java + + + org.apache.maven.wagon + wagon-ssh + 2.12 + + + org.apache.maven.wagon + wagon-ssh-external + 2.12 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.0.2 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + clean verify + maven-3 + v@{project.version} + false + true + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + attach-javadocs + + jar + + + ${javadoc.opts} + + + + + + + + + junit + junit + 4.13.1 + test + + + + + false + id-maven-repo + Index Data Maven Repository + scp://maven.indexdata.com/var/www/maven/repository + + + id-website + scpexe://www.indexdata.com/var/www/software/cql-java/ + + + + + id-maven-repo + https://maven.indexdata.com + + + + UTF-8 + + + + maven-3 + + + + ${basedir} + + + + + + + org.apache.maven.plugins + maven-site-plugin + 3.6 + + + + + + maven-site-plugin + + + attach-descriptor + + attach-descriptor + + + + + + + + + + java8-doclint-disabled + + [1.8,) + + + -Xdoclint:none + + + + diff --git a/spec/bnf b/spec/bnf index 616fa07..a49a5aa 100644 --- a/spec/bnf +++ b/spec/bnf @@ -1,4 +1,3 @@ -$Id: bnf,v 1.1 2007-06-27 23:36:19 mike Exp $ Sort of derived from http://www.loc.gov/standards/sru/cql/ diff --git a/src/org/z3950/zing/cql/.cvsignore b/src/main/java/org/z3950/zing/cql/.gitignore similarity index 100% rename from src/org/z3950/zing/cql/.cvsignore rename to src/main/java/org/z3950/zing/cql/.gitignore diff --git a/src/main/java/org/z3950/zing/cql/CQLAndNode.java b/src/main/java/org/z3950/zing/cql/CQLAndNode.java new file mode 100644 index 0000000..057802c --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLAndNode.java @@ -0,0 +1,34 @@ +package org.z3950.zing.cql; + +/** + * Represents an AND node in a CQL parse-tree. + * + */ +public class CQLAndNode extends CQLBooleanNode { + /** + * Creates a new AND node with the specified left- and right-hand + * sides and modifiers. + * + * @param left the left-hand side of the AND + * @param right the right-hand side of the AND + * @param ms the modifiers to apply to this AND + * @see ModifierSet + * @see CQLNode + * @see CQLBoolean + * @see CQLBooleanNode + */ + public CQLAndNode(CQLNode left, CQLNode right, ModifierSet ms) { + super(left, right, ms, CQLBoolean.AND); + } + + // ### Too much code duplication here with OR and NOT + @Override + 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; + } +} diff --git a/src/main/java/org/z3950/zing/cql/CQLBoolean.java b/src/main/java/org/z3950/zing/cql/CQLBoolean.java new file mode 100644 index 0000000..37dad7e --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLBoolean.java @@ -0,0 +1,16 @@ +package org.z3950.zing.cql; + +/** + * Represents a boolean operator in CQL. + * @author jakub + */ +public enum CQLBoolean { + /** AND is the same as CQL's "and" */ + AND, + /** OR is the same as CQL's "or" */ + OR, + /** NOT is the same as CQL's "not" */ + NOT, + /** PROX is the same as CQL's "prox" */ + PROX; +} diff --git a/src/main/java/org/z3950/zing/cql/CQLBooleanNode.java b/src/main/java/org/z3950/zing/cql/CQLBooleanNode.java new file mode 100644 index 0000000..fee6916 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLBooleanNode.java @@ -0,0 +1,127 @@ +package org.z3950.zing.cql; + +import java.util.List; +import java.util.Properties; + +/** + * Represents a boolean node in a CQL parse-tree. + * + */ +public abstract class CQLBooleanNode extends CQLNode { + private final CQLBoolean operator; + + public CQLBoolean getOperator() { + return operator; + } + + private CQLNode left; + + /** + * The root of a parse-tree representing the left-hand side. + * + * @return the left operand of this boolean node + */ + public CQLNode getLeftOperand() { + return left; + } + + private CQLNode right; + + /** + * The root of a parse-tree representing the right-hand side. + * + * @return the right operand of this boolean node + */ + public CQLNode getRightOperand() { + return right; + } + + ModifierSet ms; + + /** + * The set of modifiers that are applied to this boolean. + * + * @return a list of Modifier objects, which may be empty. + */ + public List getModifiers() { + return ms.getModifiers(); + } + + protected CQLBooleanNode(CQLNode left, CQLNode right, ModifierSet ms, CQLBoolean operator) { + this.left = left; + this.right = right; + this.ms = ms; + this.operator = operator; + } + + @Override + public void traverse(CQLNodeVisitor visitor) { + visitor.onBooleanNodeStart(this); + left.traverse(visitor); + visitor.onBooleanNodeOp(this); + right.traverse(visitor); + visitor.onBooleanNodeEnd(this); + } + + @Override + void toXCQLInternal(XCQLBuilder b, int level, + List prefixes, List sortkeys) { + b.indent(level).append("\n"); + renderPrefixes(b, level + 1, prefixes); + ms.toXCQLInternal(b, level + 1, "boolean", "value"); + b.indent(level + 1).append("\n"); + left.toXCQLInternal(b, level + 2); + b.indent(level + 1).append("\n"); + b.indent(level + 1).append("\n"); + right.toXCQLInternal(b, level + 2); + b.indent(level + 1).append("\n"); + renderSortKeys(b, level + 1, sortkeys); + b.indent(level).append("\n"); + } + + @Override + public String toCQL() { + // ### We don't always need parens around the operands + return ("(" + left.toCQL() + ")" + + " " + ms.toCQL() + " " + + "(" + right.toCQL() + ")"); + } + + @Override + 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(); + } + + @Override + 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(); +} diff --git a/src/main/java/org/z3950/zing/cql/CQLDefaultNodeVisitor.java b/src/main/java/org/z3950/zing/cql/CQLDefaultNodeVisitor.java new file mode 100644 index 0000000..543cde3 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLDefaultNodeVisitor.java @@ -0,0 +1,38 @@ +package org.z3950.zing.cql; + +/** + * Query tree visitor with default (no-op) implementation + * + * @author jakub + */ +public class CQLDefaultNodeVisitor implements CQLNodeVisitor { + + @Override + public void onSortNode(CQLSortNode node) { + } + + @Override + public void onPrefixNode(CQLPrefixNode node) { + } + + @Override + public void onBooleanNodeStart(CQLBooleanNode node) { + } + + @Override + public void onBooleanNodeOp(CQLBooleanNode node) { + } + + @Override + public void onBooleanNodeEnd(CQLBooleanNode node) { + } + + @Override + public void onTermNode(CQLTermNode node) { + } + + @Override + public void onRelation(CQLRelation relation) { + } + +} diff --git a/src/main/java/org/z3950/zing/cql/CQLGenerator.java b/src/main/java/org/z3950/zing/cql/CQLGenerator.java new file mode 100644 index 0000000..b350f57 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLGenerator.java @@ -0,0 +1,351 @@ +package org.z3950.zing.cql; + +import java.util.Properties; +import java.util.Random; +import java.io.InputStream; +import java.io.FileInputStream; + +/** + * A generator that produces random CQL queries. + *

+ * Why is that useful? Mainly to produce test-cases for CQL parsers + * (including the CQLParser 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. + *

+ * This code is based on the same grammar as the CQLParser class in + * this distribution - there is a generate_x() method + * for each grammar element X. + * + * @see https://zing.z3950.org/cql/index.html + */ +public class CQLGenerator { + private Properties params; + private Random rnd; + static private boolean DEBUG = false; + + /** + * Creates a new CQL generator with the specified parameters. + * + * @param params + * A Properties table containing configuration + * parameters for the queries to be generated by this generator. + *

+ * Recognised parameters are: + *

+ *
+ *
seed
+ *
+ * If specified, this is a long 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 :-) + *
+ *
complexQuery
+ *
+ * [mandatory] A floating-point number between 0.0 and 1.0, + * indicating the probability for each cql-query node + * that it will be expanded into a ``complex query'' + * (cql-query boolean search-clause) rather + * than a search-clause. + *
+ *
complexClause
+ *
+ * [mandatory] A floating-point number between 0.0 and 1.0, + * indicating the probability for each search-clause + * node that it will be expanded into a full sub-query rather + * than an [ index relation ] term triplet. + *
+ *
proxOp
+ *
+ * [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 (and, or + * and + * not) are chosen with equal probability. + *
+ *
equalsRelation
+ *
+ * [mandatory] A floating-point number between 0.0 and 1.0, + * indicating the probability that each relation will be chosen + * to be = - this is treated as a special case, since + * it's likely to be by far the most common relation in + * ``real life'' searches. + *
+ *
numericRelation
+ *
q + * [mandatory] A floating-point number between 0.0 and 1.0, + * indicating the probability that a relation, having chosen + * not to be =, is instead chosen to be one of the + * six + * numeric relations (<, >, + * <=, >=, <> + * and + * =). + *
+ *
+ */ + public CQLGenerator(Properties params) { + this.params = params; + String seed = params.getProperty("seed"); + if (seed != null) { + rnd = new Random(Long.parseLong(seed)); + } else { + rnd = new Random(); + } + } + + private static void debug(String str) { + if (DEBUG) + System.err.println("DEBUG: " + str); + } + + /** + * Generates a single random CQL query. + *

+ * 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. + *

+ * + * @return + * A CQLNode that is the root of the generated tree. + * That tree may be rendered in XCQL using its toXCQL() + * method, or decompiled into CQL using its toCQL + * 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 "within"; + 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\\\""; // A term with both bare quotes and escaped quotes + case 2: + return "comp.os.linux"; + case 3: + return "xml:element"; + case 4: + return ""; + 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 = Double.parseDouble(probability); + 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. + *

+ * It generates a single random query using the parameters + * specified in a nominated properties file, plus any additional + * name value pairs provided on the command-line, and + * decompiles it into CQL which is written to standard output. + *

+ * For example, + * java org.z3950.zing.cql.CQLGenerator + * etc/generate.properties seed 18398, + * where the file generate.properties contains: + * + *

+     *	complexQuery=0.4
+     *	complexClause=0.4
+     *	equalsRelation=0.5
+     *	numericRelation=0.7
+     *	proxOp=0.0
+     * 
+ * + * yields: + * + *
+     *	((dc.author = "<xml.element>") or (bath.title = cat)) and
+     *		(dc.subject >= "the complete dinosaur")
+     * 
+ *

+ * + * @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 " + + "[ ]..."); + System.exit(1); + } + + String configFile = args[0]; + InputStream f = new FileInputStream(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()); + } +} diff --git a/src/main/java/org/z3950/zing/cql/CQLLexer.java b/src/main/java/org/z3950/zing/cql/CQLLexer.java new file mode 100644 index 0000000..a598b01 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLLexer.java @@ -0,0 +1,206 @@ +package org.z3950.zing.cql; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * Implementation of the CQL lexical syntax analyzer + * + * @author jakub + */ +public class CQLLexer implements CQLTokenizer { + private String qs; + private int qi; + private int ql; + private int what = TT_NOTHING; + private String val; + private String lval; + private StringBuilder buf = new StringBuilder(); + + static final String OPS_AND_WHITESPACE = "()/<>= \t\r\n"; + + public CQLLexer(String cql, boolean debug) { + qs = cql; + ql = cql.length(); + } + + @Override + public void move() { + // eat whitespace + while (qi < ql && strchr(" \t\r\n", qs.charAt(qi))) + qi++; + // eof + if (qi == ql) { + what = TT_EOF; + return; + } + // current char + char c = qs.charAt(qi); + // separators + if (strchr("()/", c)) { + what = c; + qi++; + // comparitor + } else if (strchr("<>=", c)) { + what = c; + qi++; + // two-char comparitor + if (qi < ql) { + char d = qs.charAt(qi); + String comp = String.valueOf((char) c) + String.valueOf((char) d); + if (comp.equals("==")) { + what = TT_EQEQ; + qi++; + } else if (comp.equals("<=")) { + what = TT_LE; + qi++; + } else if (comp.equals(">=")) { + what = TT_GE; + qi++; + } else if (comp.equals("<>")) { + what = TT_NE; + qi++; + } + } + // quoted string + } else if (strchr("\"", c)) { // no single-quotes + what = TT_STRING; + // remember quote char + char mark = c; + qi++; + buf.setLength(0); // reset buffer + while (qi < ql && qs.charAt(qi) != mark) { + if (qs.charAt(qi) == '\\') { // escape-char + if (qi == ql - 1) { + break; // unterminated + } + buf.append(qs.charAt(qi)); + qi++; + } + buf.append(qs.charAt(qi)); + qi++; + } + val = buf.toString(); + lval = val.toLowerCase(); + if (qi < ql) + qi++; + else // unterminated + what = TT_EOF; // notify error + // unquoted string + } else { + what = TT_WORD; + buf.setLength(0); // reset buffer + while (qi < ql && !strchr(OPS_AND_WHITESPACE, qs.charAt(qi))) { + buf.append(qs.charAt(qi)); + qi++; + } + val = buf.toString(); + lval = val.toLowerCase(); + if (lval.equals("or")) + what = TT_OR; + else if (lval.equals("and")) + what = TT_AND; + else if (lval.equals("not")) + what = TT_NOT; + else if (lval.equals("prox")) + what = TT_PROX; + else if (lval.equals("sortby")) + what = TT_SORTBY; + } + } + + private boolean strchr(String s, char ch) { + return s.indexOf(ch) >= 0; + } + + @Override + public String value() { + return val; + } + + @Override + public int what() { + return what; + } + + @Override + public String render() { + return render(what, true); + } + + @Override + public String render(int token, boolean quoteChars) { + switch (token) { + case TT_EOF: + return "EOF"; + case TT_WORD: + return "word: '" + val + "'"; + case TT_STRING: + return "string: \"" + val + "\""; + case TT_LE: + return "<="; + case TT_GE: + return ">="; + case TT_NE: + return "<>"; + case TT_EQEQ: + return "=="; + case TT_AND: + return "and"; + case TT_NOT: + return "not"; + case TT_OR: + return "or"; + case TT_PROX: + return "prox"; + case TT_SORTBY: + return "sortby"; + default: + // a single character, such as '(' or '/' or relation + String res = String.valueOf((char) token); + if (quoteChars) + res = "'" + res + "'"; + return res; + } + } + + @Override + public int pos() { + return qi; + } + + public static void main(String[] args) throws Exception { + if (args.length > 1) { + System.err.println("Usage: CQLLexer []"); + System.err.println("If unspecified, query is read from stdin"); + System.exit(1); + } + + String cql; + if (args.length == 1) { + cql = args[0]; + } else { + BufferedReader buff = new BufferedReader(new InputStreamReader(System.in)); + try { + // read a single line of input + cql = buff.readLine(); + if (cql == null) { + System.err.println("Can't read query from stdin"); + System.exit(2); + return; + } + } catch (IOException ex) { + System.err.println("Can't read query: " + ex.getMessage()); + System.exit(2); + return; + } + } + + CQLTokenizer lexer = new CQLLexer(cql, true); + while ((lexer.what()) != TT_EOF) { + lexer.move(); + System.out.println(lexer.render()); + } + } +} diff --git a/src/main/java/org/z3950/zing/cql/CQLNode.java b/src/main/java/org/z3950/zing/cql/CQLNode.java new file mode 100644 index 0000000..2b33b0e --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLNode.java @@ -0,0 +1,352 @@ +package org.z3950.zing.cql; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * Represents a node in a CQL parse-tree. + * + */ +public abstract class CQLNode { + + public abstract void traverse(CQLNodeVisitor visitor); + + /** + * 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. + * + * @return + * A String containing an XCQL document equivalent to the + * parse-tree whose root is this node. + */ + public String toXCQL() { + StringBuilder sb = new StringBuilder(); + toXCQLInternal(new XCQLBuilder(sb), 0); + return sb.toString(); + } + + void toXCQLInternal(XCQLBuilder b, int level) { + toXCQLInternal(b, level, null, null); + } + + abstract void toXCQLInternal(XCQLBuilder b, int level, + List prefixes, List sortkeys); + + static void renderPrefixes(XCQLBuilder b, + int level, List prefixes) { + if (prefixes == null || prefixes.size() == 0) + return; + b.indent(level).append("\n"); + for (int i = 0; i < prefixes.size(); i++) { + CQLPrefix p = prefixes.get(i); + b.indent(level + 1).append("\n"); + if (p.name != null) + b.indent(level + 2).append("").append(p.name).append("\n"); + b.indent(level + 2).append("").append(p.identifier).append("\n"); + b.indent(level + 1).append("\n"); + } + b.indent(level).append("\n"); + } + + static void renderSortKeys(XCQLBuilder b, int level, + List sortkeys) { + if (sortkeys == null || sortkeys.size() == 0) + return; + b.indent(level).append("\n"); + for (int i = 0; i < sortkeys.size(); i++) { + ModifierSet key = sortkeys.get(i); + key.toXCQLInternal(b, level + 1, "key", "index"); + } + b.indent(level).append("\n"); + } + + /** + * Decompiles a parse-tree into a CQL query. + * + * @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 + * Chapter 7 (Supporting Tools) of the + * YAZ manual. + * + * @param config + * A Properties 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, + * etc/pqf.properties, which see. + * @return + * A String containing a PQF query equivalent to the parse-tree + * whose root is this node. + * @throws PQFTranslationException + * If the parse-tree cannot be translated into + * PQF, for example + * because it contains a CQL relation that is + * not supported by the + * PQF configuration. + */ + abstract public String toPQF(Properties config) + throws PQFTranslationException; + + /** + * 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 Properties 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, + * etc/pqf.properties, which see. + * @return + * A byte array containing the BER packet. + * @see + * A Layman's Guide to a Subset of ASN.1, BER, and DER + * @throws PQFTranslationException + * If the parse-tree cannot be translated into + * PQF, for example + * because it contains a CQL relation that is + * not supported by the + * PQF configuration. + */ + 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. + */ + 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 Map madeOIDs = new HashMap(10); + + protected static final byte[] makeOID(String oid) { + byte[] o = (byte[]) madeOIDs.get(oid); + if (o != null) { + return o; + } + o = parseOID(oid); + if (o == null) { + return null; + } + madeOIDs.put(oid, o); + return o; + } + + private static final byte[] parseOID(String oid) { + int offset = 0, oidOffset = 0; + byte [] 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; + } + int dot = oid.indexOf('.', oidOffset); + if (dot == -1) { + if (offset == 0) { + return null; + } + dot = oid.length(); + } + int value = Integer.parseInt(oid.substring(oidOffset, dot)); + if (offset == 0) { // 1st two are special + oidOffset = dot + 1; // skip past '.' + dot = oid.indexOf('.', oidOffset); + if (dot == -1) { + dot = oid.length(); + } + 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) { + byte[] ptr = new byte[offset]; + System.arraycopy(o, 0, ptr, 0, offset); + return ptr; + } + oidOffset = dot + 1; + } + return null; + } + + 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; + } + + @Override + public String toString() { + return toCQL(); + } +} diff --git a/src/main/java/org/z3950/zing/cql/CQLNodeVisitor.java b/src/main/java/org/z3950/zing/cql/CQLNodeVisitor.java new file mode 100644 index 0000000..0553ba4 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLNodeVisitor.java @@ -0,0 +1,29 @@ +package org.z3950.zing.cql; + +/** + * Allows to visit different types of nodes in the query tree. In most cases + * you will want to extend CQLDefaultNodeVisitor and + * choose only the methods you need to override. + * CQLBooleanNode/Start/Op/End methods allow to generate prefix, infix or + * postfix notations without needing to keep track of the parse tree. + * See the CQLNodeVisitorTest for examples. + * + * @author jakub + */ +public interface CQLNodeVisitor { + + public void onSortNode(CQLSortNode node); + + public void onPrefixNode(CQLPrefixNode node); + + public void onBooleanNodeStart(CQLBooleanNode node); + + public void onBooleanNodeOp(CQLBooleanNode node); + + public void onBooleanNodeEnd(CQLBooleanNode node); + + public void onTermNode(CQLTermNode node); + + public void onRelation(CQLRelation relation); + +} diff --git a/src/org/z3950/zing/cql/CQLNotNode.java b/src/main/java/org/z3950/zing/cql/CQLNotNode.java similarity index 51% rename from src/org/z3950/zing/cql/CQLNotNode.java rename to src/main/java/org/z3950/zing/cql/CQLNotNode.java index 7b56de3..970014a 100644 --- a/src/org/z3950/zing/cql/CQLNotNode.java +++ b/src/main/java/org/z3950/zing/cql/CQLNotNode.java @@ -1,12 +1,8 @@ -// $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 { /** @@ -14,15 +10,16 @@ public class CQLNotNode extends CQLBooleanNode { * sides and modifiers. */ public CQLNotNode(CQLNode left, CQLNode right, ModifierSet ms) { - super(left, right, ms); + super(left, right, ms, CQLBoolean.NOT); } + @Override 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; + 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; } } diff --git a/src/org/z3950/zing/cql/CQLOrNode.java b/src/main/java/org/z3950/zing/cql/CQLOrNode.java similarity index 51% rename from src/org/z3950/zing/cql/CQLOrNode.java rename to src/main/java/org/z3950/zing/cql/CQLOrNode.java index 9e7d66e..bbe509e 100644 --- a/src/org/z3950/zing/cql/CQLOrNode.java +++ b/src/main/java/org/z3950/zing/cql/CQLOrNode.java @@ -1,12 +1,8 @@ -// $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 { /** @@ -14,15 +10,16 @@ public class CQLOrNode extends CQLBooleanNode { * sides and modifiers. */ public CQLOrNode(CQLNode left, CQLNode right, ModifierSet ms) { - super(left, right, ms); + super(left, right, ms, CQLBoolean.OR); } + @Override 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; + 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; } } diff --git a/src/main/java/org/z3950/zing/cql/CQLParseException.java b/src/main/java/org/z3950/zing/cql/CQLParseException.java new file mode 100644 index 0000000..299cf1d --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLParseException.java @@ -0,0 +1,30 @@ +package org.z3950.zing.cql; + +/** + * Exception indicating that an error ocurred parsing CQL. + * + */ +public class CQLParseException extends Exception { + private int pos; + + /** + * Creates a new CQLParseException. + * + * @param s + * An error message describing the problem with the query, + * usually a syntax error of some kind. + */ + public CQLParseException(String s, int pos) { + super(s); + this.pos = pos; + } + + /** + * Character position of the parsing error. + * + * @return + */ + public int getPosition() { + return pos; + } +} diff --git a/src/main/java/org/z3950/zing/cql/CQLParser.java b/src/main/java/org/z3950/zing/cql/CQLParser.java new file mode 100644 index 0000000..27456ce --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLParser.java @@ -0,0 +1,535 @@ +package org.z3950.zing.cql; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.Properties; +import java.io.InputStream; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Compiles CQL strings into parse trees of CQLNode subtypes. + * + * @see https://zing.z3950.org/cql/index.html + */ +public class CQLParser { + private CQLTokenizer lexer; + private final int compat; // When false, implement CQL 1.2 + private final Set customRelations = new HashSet(); + + public static final int V1POINT1 = 12368; + public static final int V1POINT2 = 12369; + public static final int V1POINT1SORT = 12370; + public final boolean allowKeywordTerms; + + static private boolean DEBUG = false; + static private boolean LEXDEBUG = false; + + /** + * The new parser implements a dialect of CQL specified by the + * compat argument: + *

    + *
  • V1POINT1 - CQL version 1.1 + *
  • + *
  • V1POINT2 - CQL version 1.2 + *
  • + *
  • V1POINT1SORT - CQL version 1.1 but including + * sortby as specified for CQL 1.2. + *
  • + *
+ */ + public CQLParser(int compat) { + this.compat = compat; + this.allowKeywordTerms = true; + } + + /** + * Official CQL grammar allows registered keywords like 'and/or/not/sortby/prox' + * to be used unquoted in terms. This constructor allows to create an instance + * of a parser that prohibits this behavior while sacrificing compatibility. + * + * @param compat CQL version compatibility + * @param allowKeywordTerms when false registered keywords are disallowed in + * unquoted terms + */ + public CQLParser(int compat, boolean allowKeywordTerms) { + this.compat = compat; + this.allowKeywordTerms = allowKeywordTerms; + } + + /** + * The new parser implements CQL 1.2 + */ + public CQLParser() { + this.compat = V1POINT2; + this.allowKeywordTerms = true; + } + + private static void debug(String str) { + if (DEBUG) + System.err.println("PARSEDEBUG: " + str); + } + + /** + * Registers custom relation in this parser. Note that when a custom relation + * is registered the parser is no longer strictly compliant with the chosen + * spec. + * Custom relations are case-insensitive. + * + * @param relation + * @return true if custom relation has not been registered already + */ + public boolean registerCustomRelation(String relation) { + return customRelations.add(relation.toLowerCase()); + } + + /** + * Unregisters previously registered custom relation in this instance of the + * parser. + * + * @param relation + * @return true is relation has been previously registered + */ + public boolean unregisterCustomRelation(String relation) { + return customRelations.remove(relation.toLowerCase()); + } + + /** + * Compiles a CQL query. + *

+ * 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. toCQL() + * returns a decompiled CQL query equivalent to the one that was + * compiled in the first place; toXCQL() returns an + * XML snippet representing the query; and toPQF() + * 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.move(); + debug("about to parseQuery()"); + CQLNode root = parseTopLevelPrefixes("cql.serverChoice", + new CQLRelation(compat == V1POINT2 ? "=" : "scr")); + if (lexer.what() != CQLTokenizer.TT_EOF) + throw new CQLParseException("junk after end: " + lexer.render(), + lexer.pos()); + + return root; + } + + private CQLNode parseTopLevelPrefixes(String index, CQLRelation relation) + throws CQLParseException, IOException { + debug("top-level prefix mapping"); + + if (lexer.what() == '>') { + return parsePrefix(index, relation, true); + } + + CQLNode node = parseQuery(index, relation); + if ((compat == V1POINT2 || compat == V1POINT1SORT) && + lexer.what() == CQLTokenizer.TT_SORTBY) { + match(lexer.what()); + debug("sortspec"); + + CQLSortNode sortnode = new CQLSortNode(node); + while (lexer.what() != CQLTokenizer.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", lexer.pos()); + } + + node = sortnode; + } + + return node; + } + + private CQLNode parseQuery(String index, CQLRelation relation) + throws CQLParseException, IOException { + debug("in parseQuery()"); + + CQLNode term = parseTerm(index, relation); + while (lexer.what() != CQLTokenizer.TT_EOF && + lexer.what() != ')' && + lexer.what() != CQLTokenizer.TT_SORTBY) { + if (lexer.what() == CQLTokenizer.TT_AND || + lexer.what() == CQLTokenizer.TT_OR || + lexer.what() == CQLTokenizer.TT_NOT || + lexer.what() == CQLTokenizer.TT_PROX) { + int type = lexer.what(); + String val = lexer.value(); + match(type); + ModifierSet ms = gatherModifiers(val); + CQLNode term2 = parseTerm(index, relation); + term = ((type == CQLTokenizer.TT_AND) ? new CQLAndNode(term, term2, ms) + : (type == CQLTokenizer.TT_OR) ? new CQLOrNode(term, term2, ms) + : (type == CQLTokenizer.TT_NOT) ? new CQLNotNode(term, term2, ms) + : new CQLProxNode(term, term2, ms)); + } else { + throw new CQLParseException("expected boolean, got " + + lexer.render(), lexer.pos()); + } + } + + debug("no more ops"); + return term; + } + + private ModifierSet gatherModifiers(String base) + throws CQLParseException, IOException { + debug("in gatherModifiers()"); + + ModifierSet ms = new ModifierSet(base); + while (lexer.what() == '/') { + match('/'); + if (lexer.what() != CQLTokenizer.TT_WORD) + throw new CQLParseException("expected modifier, " + + "got " + lexer.render(), + lexer.pos()); + String type = lexer.value().toLowerCase(); + match(lexer.what()); + if (!isSymbolicRelation()) { + // 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.what(), false); + match(lexer.what()); + 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 first; + StringBuilder all; + while (true) { + if (lexer.what() == '(') { + debug("parenthesised term"); + match('('); + CQLNode expr = parseQuery(index, relation); + match(')'); + return expr; + } else if (lexer.what() == '>') { + return parsePrefix(index, relation, false); + } + + debug("non-parenthesised term"); + first = matchSymbol("index or term"); + all = new StringBuilder(first); + // match relation only on second postion + while (isWordOrString() && (all.length() > first.length() || !isRelation())) { + all.append(" ").append(lexer.value()); + match(lexer.what()); + } + + if (!isRelation()) + break; // we're done if no relation + + // render relation + String relstr = (lexer.what() == CQLTokenizer.TT_WORD ? lexer.value() : lexer.render(lexer.what(), false)); + // we have relation, but it only makes sense if preceded by a single term + if (all.length() > first.length()) { + throw new CQLParseException("unexpected relation '" + relstr + "'", lexer.pos()); + } + index = first; + relation = new CQLRelation(relstr); + match(lexer.what()); + ModifierSet ms = gatherModifiers(relstr); + relation.ms = ms; + debug("index='" + index + ", " + + "relation='" + relation.toCQL() + "'"); + } + CQLTermNode node = new CQLTermNode(index, relation, all.toString()); + 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.what() == '=') { + match('='); + name = identifier; + identifier = matchSymbol("prefix-identifer"); + } + CQLNode node = topLevel ? parseTopLevelPrefixes(index, relation) : parseQuery(index, relation); + + return new CQLPrefixNode(name, identifier, node); + } + + private boolean isWordOrString() { + return CQLTokenizer.TT_WORD == lexer.what() + || CQLTokenizer.TT_STRING == lexer.what(); + } + + private boolean isRelation() { + debug("isRelation: checking what()=" + lexer.what() + + " (" + lexer.render() + ")"); + if (lexer.what() == CQLTokenizer.TT_WORD) { + return lexer.value().indexOf('.') >= 0 || + lexer.value().equalsIgnoreCase("any") || + lexer.value().equalsIgnoreCase("all") || + lexer.value().equalsIgnoreCase("within") || + lexer.value().equalsIgnoreCase("encloses") || + (lexer.value().equalsIgnoreCase("exact") && compat != V1POINT2) || + (lexer.value().equalsIgnoreCase("scr") && compat != V1POINT2) || + (lexer.value().equalsIgnoreCase("adj") && compat == V1POINT2) || + customRelations.stream().anyMatch(r -> r.equalsIgnoreCase(lexer.value())); + } + return isSymbolicRelation(); + } + + private boolean isSymbolicRelation() { + debug("isSymbolicRelation: checking what()=" + lexer.what() + + " (" + lexer.render() + ")"); + return (lexer.what() == '<' || + lexer.what() == '>' || + lexer.what() == '=' || + lexer.what() == CQLTokenizer.TT_LE || + lexer.what() == CQLTokenizer.TT_GE || + lexer.what() == CQLTokenizer.TT_NE || + lexer.what() == CQLTokenizer.TT_EQEQ); + } + + private void match(int token) + throws CQLParseException, IOException { + debug("in match(" + lexer.render(token, true) + ")"); + if (lexer.what() != token) + throw new CQLParseException("expected " + + lexer.render(token, true) + + ", " + "got " + lexer.render(), + lexer.pos()); + lexer.move(); + debug("match() got token=" + lexer.what() + ", value()='" + lexer.value() + "'"); + } + + private String matchSymbol(String expected) + throws CQLParseException, IOException { + + debug("in matchSymbol()"); + if (lexer.what() == CQLTokenizer.TT_WORD || + lexer.what() == CQLTokenizer.TT_STRING || + // 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. + (allowKeywordTerms && + lexer.what() == CQLTokenizer.TT_AND || + lexer.what() == CQLTokenizer.TT_OR || + lexer.what() == CQLTokenizer.TT_NOT || + lexer.what() == CQLTokenizer.TT_PROX || + lexer.what() == CQLTokenizer.TT_SORTBY)) { + String symbol = lexer.value(); + match(lexer.what()); + return symbol; + } + + throw new CQLParseException("expected " + expected + ", " + + "got " + lexer.render(), lexer.pos()); + } + + /** + * Simple test-harness for the CQLParser class. + *

+ * Reads a CQL query either from its command-line argument, if + * there is one, or standard input otherwise. So these two + * invocations are equivalent: + * + *

+     *  CQLParser 'au=(Kerninghan or Ritchie) and ti=Unix'
+     *  echo au=(Kerninghan or Ritchie) and ti=Unix | CQLParser
+     * 
+ * + * The test-harness parses the supplied query and renders is as + * XCQL, so that both of the invocations above produce the + * following output: + * + *
+     *	<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>
+     * 
+ * + * @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 + * -c option is supplied]. + */ + public static void main(String[] args) { + char mode = 'x'; // x=XCQL, c=CQL, p=PQF + String pfile = null; + + List argv = new ArrayList(); + 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 []"); + 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 { + BufferedReader buff = new BufferedReader(new InputStreamReader(System.in)); + try { + // read a single line of input + cql = buff.readLine(); + if (cql == null) { + System.err.println("Can't read query from stdin"); + System.exit(2); + return; + } + } catch (IOException ex) { + System.err.println("Can't read query: " + ex.getMessage()); + System.exit(2); + return; + } + } + + CQLParser parser = new CQLParser(compat); + CQLNode root; + try { + root = parser.parse(cql); + } catch (CQLParseException ex) { + System.err.println("Syntax error: " + ex.getMessage()); + StringBuilder space = new StringBuilder(cql.length()); + System.out.println(cql); + for (int i = 0; i < ex.getPosition(); i++) + space.append(" "); + space.append("^"); + System.err.println(space.toString()); + System.exit(3); + return; // compiler + } catch (IOException ex) { + System.err.println("Can't compile query: " + ex.getMessage()); + System.exit(4); + return; // compiler + } + + try { + if (mode == 'c') { + System.out.println(root.toCQL()); + } else if (mode == 'p') { + try { + InputStream f = new FileInputStream(pfile); + Properties config = new Properties(); + config.load(f); + f.close(); + System.out.println(root.toPQF(config)); + } catch (IOException ex) { + System.err.println("Can't load PQF properties:" + + ex.getMessage()); + System.exit(5); + } + } else { + System.out.print(root.toXCQL()); + } + } 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) { + System.err.println("Cannot translate to PQF: " + ex.getMessage()); + System.exit(10); + } + } +} diff --git a/src/main/java/org/z3950/zing/cql/CQLPrefix.java b/src/main/java/org/z3950/zing/cql/CQLPrefix.java new file mode 100644 index 0000000..c36f6f0 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLPrefix.java @@ -0,0 +1,39 @@ +package org.z3950.zing.cql; + +/** + * Represents a CQL prefix mapping from short name to long identifier. + * + */ +public class CQLPrefix { + + String name; + + /** + * The short name of the prefix mapping. That is, the prefix + * itself, such as dc, as it might be used in an index + * like dc.title. + */ + public String getName() { + return name; + } + + String identifier; + + /** + * The full identifier name of the prefix mapping. That is, + * typically, a URI permanently allocated to a specific index + * set, such as http://zthes.z3950.org/cql/1.0. + */ + public String getIdentifier() { + return 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; + } +} diff --git a/src/main/java/org/z3950/zing/cql/CQLPrefixNode.java b/src/main/java/org/z3950/zing/cql/CQLPrefixNode.java new file mode 100644 index 0000000..f14a471 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLPrefixNode.java @@ -0,0 +1,82 @@ +package org.z3950.zing.cql; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +/** + * Represents a prefix node in a CQL parse-tree. + * + */ +public class CQLPrefixNode extends CQLNode { + + private CQLPrefix prefix; + + /** + * The prefix definition that governs the subtree. + */ + public CQLPrefix getPrefix() { + return prefix; + } + + private CQLNode subtree; + + /** + * The root of a parse-tree representing the part of the query + * that is governed by this prefix definition. + */ + public CQLNode getSubtree() { + return 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; + } + + @Override + public void traverse(CQLNodeVisitor visitor) { + visitor.onPrefixNode(this); + subtree.traverse(visitor); + } + + @Override + void toXCQLInternal(XCQLBuilder b, int level, List prefixes, + List sortkeys) { + List tmp = (prefixes == null ? new ArrayList() : new ArrayList(prefixes)); + tmp.add(prefix); + subtree.toXCQLInternal(b, level, tmp, sortkeys); + } + + @Override + 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() + ")"; + } + } + + @Override + 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); + } + + @Override + public byte[] toType1BER(Properties config) throws PQFTranslationException { + // See comment on toPQF() + return subtree.toType1BER(config); + } +} diff --git a/src/main/java/org/z3950/zing/cql/CQLProxNode.java b/src/main/java/org/z3950/zing/cql/CQLProxNode.java new file mode 100644 index 0000000..14a1e2a --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLProxNode.java @@ -0,0 +1,124 @@ +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. + * + */ +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, CQLBoolean.PROX); + } + + /* + * 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. + */ + @Override + 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; + } + + @Override + 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; + } +} diff --git a/src/main/java/org/z3950/zing/cql/CQLRelation.java b/src/main/java/org/z3950/zing/cql/CQLRelation.java new file mode 100644 index 0000000..3dc5b63 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLRelation.java @@ -0,0 +1,72 @@ +package org.z3950.zing.cql; + +import java.util.List; +import java.util.Properties; + +/** + * Represents a relation between a CQL index and term. + * + */ +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 + * (<=, >, etc.), the text + * relations any, all and exact, the + * old server-choice relation scr and profiled relations of + * the form prefix.name. + */ + // ### 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(); + } + + /** + * Returns an array of the modifiers associated with a CQLRelation. + * + * @return + * An array of Modifier objects. + */ + public List getModifiers() { + return ms.getModifiers(); + } + + @Override + public void traverse(CQLNodeVisitor visitor) { + visitor.onRelation(this); + } + + @Override + void toXCQLInternal(XCQLBuilder b, int level, List prefixes, + List sortkeys) { + if (sortkeys != null) + throw new Error("CQLRelation.toXCQL() called with sortkeys"); + ms.toXCQLInternal(b, level, "relation", "value"); + } + + @Override + public String toCQL() { + return ms.toCQL(); + } + + @Override + public String toPQF(Properties config) throws PQFTranslationException { + throw new Error("CQLRelation.toPQF() can never be called"); + } + + @Override + public byte[] toType1BER(Properties config) { + throw new Error("CQLRelation.toType1BER() can never be called"); + } +} diff --git a/src/main/java/org/z3950/zing/cql/CQLSortNode.java b/src/main/java/org/z3950/zing/cql/CQLSortNode.java new file mode 100644 index 0000000..9b65dd4 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLSortNode.java @@ -0,0 +1,84 @@ +package org.z3950.zing.cql; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +/** + * Represents a sort node in a CQL parse-tree. + * + */ +public class CQLSortNode extends CQLNode { + /** + * The root of a subtree representing the query whose result is to + * be sorted. + */ + private 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. + */ + List keys; + + public CQLNode getSubtree() { + return subtree; + } + + public CQLSortNode(CQLNode subtree) { + this.subtree = subtree; + keys = new ArrayList(); + } + + public void addSortIndex(ModifierSet key) { + keys.add(key); + } + + public List getSortIndexes() { + return keys; + } + + @Override + public void traverse(CQLNodeVisitor visitor) { + visitor.onSortNode(this); + subtree.traverse(visitor); + } + + @Override + void toXCQLInternal(XCQLBuilder b, int level, List prefixes, + List sortkeys) { + if (sortkeys != null) + throw new Error("CQLSortNode.toXCQL() called with sortkeys"); + subtree.toXCQLInternal(b, level, prefixes, keys); + } + + @Override + public String toCQL() { + StringBuilder buf = new StringBuilder(subtree.toCQL()); + + if (keys != null) { + buf.append(" sortby"); + for (int i = 0; i < keys.size(); i++) { + ModifierSet key = keys.get(i); + buf.append(" ").append(key.toCQL()); + } + } + + return buf.toString(); + } + + @Override + public String toPQF(Properties config) throws PQFTranslationException { + return "@attr 1=oops \"###\""; + } + + @Override + 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); + } +} diff --git a/src/main/java/org/z3950/zing/cql/CQLTermNode.java b/src/main/java/org/z3950/zing/cql/CQLTermNode.java new file mode 100644 index 0000000..13a26c8 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLTermNode.java @@ -0,0 +1,341 @@ +package org.z3950.zing.cql; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +/** + * 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. + * + */ +public class CQLTermNode extends CQLNode { + private String index; + private CQLRelation relation; + private String term; + + /** + * Creates a new term node with the specified index, + * relation and term. The first two may be + * null, but the term 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")); + } + + @Override + public void traverse(CQLNodeVisitor visitor) { + // we visit relation first to allow filtering on relation type in the visitor + relation.traverse(visitor); + visitor.onTermNode(this); + } + + @Override + public String getResultSetName() { + if (isResultSetIndex(index)) + return term; + else + return null; + } + + @Override + void toXCQLInternal(XCQLBuilder b, int level, List prefixes, + List sortkeys) { + b.indent(level).append("\n"); + renderPrefixes(b, level + 1, prefixes); + b.indent(level + 1).append("").xq(index).append("\n"); + relation.toXCQLInternal(b, level + 1); + b.indent(level + 1).append("").xq(term).append("\n"); + renderSortKeys(b, level + 1, sortkeys); + b.indent(level).append("\n"); + } + + @Override + public String toCQL() { + String quotedIndex = toCQLTerm(index); + String quotedTerm = toCQLTerm(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 List getAttrs(Properties config) throws PQFTranslationException { + List attrs = new ArrayList(); + + // 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) + attr = config.getProperty("qualifier." + 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 = "exact"; + } 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); + + List 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 truncation = "none"; + String text = term; + if (text.length() > 0 && text.substring(0, 1).equals("^")) { + text = text.substring(1); // ### change not seen by caller + pos = "first"; + } + if (text.startsWith("*") && text.endsWith("*")) { + truncation = "both"; + } else if (text.startsWith("*")) { + truncation = "left"; + } else if (text.endsWith("*")) { + truncation = "right"; + } + 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 attribute, 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("truncation." + truncation); + if (attr == null) + throw new UnknownTruncationException(truncation); + attrs.add(attr); + + attr = config.getProperty("structure." + rel); + if (attr == null) + attr = config.getProperty("structure.*"); + attrs.add(attr); + + return attrs; + } + + @Override + 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 " + toCQLTerm(term); + } + + List attrs = getAttrs(config); + + String attr, s = ""; + for (int i = 0; i < attrs.size(); i++) { + attr = (String) attrs.get(i); + s += "@attr " + attr.replace(" ", " @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); + + len = text.length(); + if (text.startsWith("*") && text.endsWith("*")) { + text = text.substring(1, len - 1); + } else if (text.startsWith("*")) { + text = text.substring(1); + } else if (text.endsWith("*")) { + text = text.substring(0, len - 1); + } + + return s + toCQLTerm(text); + } + + // ensure that a term is properly quoted for CQL output if necessary. + // If the term has a bare double-quote (") it will be + // escaped with a backslash. + static String toCQLTerm(String str) { + if (str == null) { + return null; + } + boolean quote = str.isEmpty(); + boolean escaped = false; + StringBuilder sb = new StringBuilder(); + for (char ch : str.toCharArray()) { + if (CQLLexer.OPS_AND_WHITESPACE.indexOf(ch) >= 0) { + quote = true; + } + if (ch == '"' && !escaped) { + sb.append('\\'); + } + escaped = ch == '\\' && !escaped; + sb.append(ch); + } + if (escaped) { + // trailing backslash - escape it + sb.append('\\'); + } + if (quote) { + return "\"" + sb.toString() + "\""; + } else { + return sb.toString(); + } + } + + @Override + 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; + 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 + + List 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); + try { + type = Integer.parseInt(attr.substring(0, j)); + } catch (NumberFormatException e) { + throw new PQFTranslationException("Bad attribute type: " + attr.substring(0, j)); + } + offset = putLen(numLen(type), operand, offset); + offset = putNum(type, operand, offset); + + offset = putTag(CONTEXT, 121, PRIMITIVE, operand, offset); + try { + value = Integer.parseInt(attr.substring(j + 1)); + } catch (NumberFormatException e) { + throw new PQFTranslationException("Bad attribute value: " + 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 = text.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; + } +} diff --git a/src/main/java/org/z3950/zing/cql/CQLTokenizer.java b/src/main/java/org/z3950/zing/cql/CQLTokenizer.java new file mode 100644 index 0000000..2095f35 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/CQLTokenizer.java @@ -0,0 +1,62 @@ +package org.z3950.zing.cql; + +/** + * API bridge between the parser and lexer implementation + * + * @author jakub + */ +public interface CQLTokenizer { + + public static final int TT_EOF = -1; + public static final int TT_WORD = -3; + public static final int TT_NOTHING = -4; + + public final static int TT_STRING = 999; // quoted string + public final static int TT_LE = 1000; // The "<=" relation + public final static int TT_GE = 1001; // The ">=" relation + public final static int TT_NE = 1002; // The "<>" relation + public final static int TT_EQEQ = 1003; // The "==" relation + public final static int TT_AND = 1004; // The "and" boolean + public final static int TT_OR = 1005; // The "or" boolean + public final static int TT_NOT = 1006; // The "not" boolean + public final static int TT_PROX = 1007; // The "prox" boolean + public final static int TT_SORTBY = 1008; // The "sortby" operator + + /** + * Consume next input token + */ + public void move(); + + /** + * Return the value of the last consumed token + * + * @return value of the token + */ + public String value(); + + /** + * Return the type of the last consumed token + * + * @return last consumed token + */ + public int what(); + + /** + * Render the type and value of the last consumed token + * + * @return human-readable string + */ + public String render(); + + /** + * Render specified token type + * + * @param what token type + * @param quote true, if single characters should be quoted for readability + * @return human-readable string + */ + public String render(int what, boolean quote); + + public int pos(); + +} diff --git a/src/main/java/org/z3950/zing/cql/MissingParameterException.java b/src/main/java/org/z3950/zing/cql/MissingParameterException.java new file mode 100644 index 0000000..a25200a --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/MissingParameterException.java @@ -0,0 +1,17 @@ +package org.z3950.zing.cql; + +/** + * Exception indicating that a required property was not specified. + * + */ +public class MissingParameterException extends Exception { + /** + * Creates a new MissingParameterException. + * + * @param s + * The name of the property whose value was required but not supplied. + */ + public MissingParameterException(String s) { + super(s); + } +} diff --git a/src/main/java/org/z3950/zing/cql/Modifier.java b/src/main/java/org/z3950/zing/cql/Modifier.java new file mode 100644 index 0000000..37b21d8 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/Modifier.java @@ -0,0 +1,78 @@ +package org.z3950.zing.cql; + +/** + * Represents a single modifier, consisting of three elements: a type, + * a comparison and a value. For example, "distance", "<", "3". The + * type is mandatory; either the comparison and value must both occur, + * or neither must. + *

+ * This class is used only by ModifierSet. + * + */ +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; + } + + void toXCQLInternal(XCQLBuilder b, int level, String relationElement) { + b.indent(level).append("\n"); + b.indent(level + 1).append(""); + b.xq(type).append("\n"); + if (value != null) { + b.indent(level + 1).append("<").append(relationElement).append(">"); + b.xq(comparison).append("\n"); + b.indent(level + 1).append(""); + b.xq(value).append("\n"); + } + b.indent(level).append("\n"); + } + + public String toCQL() { + StringBuilder buf = new StringBuilder(type); + if (value != null) + buf.append(" ").append(comparison).append(" ").append(value); + return buf.toString(); + } +} diff --git a/src/main/java/org/z3950/zing/cql/ModifierSet.java b/src/main/java/org/z3950/zing/cql/ModifierSet.java new file mode 100644 index 0000000..3456155 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/ModifierSet.java @@ -0,0 +1,117 @@ +package org.z3950.zing.cql; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a base String and a set of Modifiers. + *

+ * 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. + *

+ *

+ * A ModifierSet consists of a ``base'' string together with a set of + * zero or more type comparison value pairs, + * where type, comparison and value are all strings. + *

+ * + */ +public class ModifierSet { + private String base; + private List modifiers; + + /** + * Creates a new ModifierSet with the specified base. + */ + public ModifierSet(String base) { + this.base = base; + modifiers = new ArrayList(); + } + + /** + * Returns the base string with which the ModifierSet was created. + */ + public String getBase() { + return base; + } + + /** + * Adds a modifier of the specified type, + * comparison and value 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 type, but with no + * comparison and value, 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 List getModifiers() { + return modifiers; + } + + void toXCQLInternal(XCQLBuilder b, int level, + String topLevelElement, String valueElement) { + b.indent(level).append("<").append(topLevelElement).append(">\n").indent(level + 1).append("<") + .append(valueElement).append(">").xq(base).append("\n"); + if (modifiers.size() > 0) { + b.indent(level + 1).append("\n"); + for (int i = 0; i < modifiers.size(); i++) { + modifiers.get(i).toXCQLInternal(b, level + 2, "comparison"); + } + b.indent(level + 1).append("\n"); + } + b.indent(level).append("\n"); + } + + public String toCQL() { + StringBuilder buf = new StringBuilder(base); + for (int i = 0; i < modifiers.size(); i++) { + buf.append("/").append(modifiers.get(i).toCQL()); + } + + return buf.toString(); + } + + public static void main(String[] args) { + if (args.length < 1) { + System.err.println("Usage: ModifierSet [ ]..."); + System.exit(1); + } + + ModifierSet res = new ModifierSet(args[0]); + for (int i = 1; i + 2 < args.length ; i += 3) { + res.addModifier(args[i], args[i + 1], args[i + 2]); + } + + System.out.println(res.toCQL()); + } +} diff --git a/src/org/z3950/zing/cql/PQFTranslationException.java b/src/main/java/org/z3950/zing/cql/PQFTranslationException.java similarity index 52% rename from src/org/z3950/zing/cql/PQFTranslationException.java rename to src/main/java/org/z3950/zing/cql/PQFTranslationException.java index 85ec184..b214b42 100644 --- a/src/org/z3950/zing/cql/PQFTranslationException.java +++ b/src/main/java/org/z3950/zing/cql/PQFTranslationException.java @@ -1,16 +1,11 @@ -// $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); + super(s); } } diff --git a/src/org/z3950/zing/cql/UnknownIndexException.java b/src/main/java/org/z3950/zing/cql/UnknownIndexException.java similarity index 56% rename from src/org/z3950/zing/cql/UnknownIndexException.java rename to src/main/java/org/z3950/zing/cql/UnknownIndexException.java index b4201d3..48e2ec9 100644 --- a/src/org/z3950/zing/cql/UnknownIndexException.java +++ b/src/main/java/org/z3950/zing/cql/UnknownIndexException.java @@ -1,26 +1,22 @@ -// $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 + * 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 UnknownIndexException. + * Creates a new UnknownIndexException. + * * @param s - * The index for which there was no PQF configuration. + * The index for which there was no PQF configuration. */ public UnknownIndexException(String s) { - super(s); + super(s); } } diff --git a/src/main/java/org/z3950/zing/cql/UnknownPositionException.java b/src/main/java/org/z3950/zing/cql/UnknownPositionException.java new file mode 100644 index 0000000..0b24ffe --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/UnknownPositionException.java @@ -0,0 +1,24 @@ +package org.z3950.zing.cql; + +/** + * Exception indicating that a position was not recognised. + * When rendering a tree out as PQF, each term is classified either as + * any, first, last or + * firstAndLast, depending on whether it begins and/or ends + * with the word-anchoring meta-character ^. Its + * classification is looked up as a position in the PQF + * configuration. If the position is not configured, we throw one of + * these babies. + * + */ +public class UnknownPositionException extends PQFTranslationException { + /** + * Creates a new UnknownPositionException. + * + * @param s + * The position for which there was no PQF configuration. + */ + public UnknownPositionException(String s) { + super(s); + } +} diff --git a/src/org/z3950/zing/cql/UnknownRelationException.java b/src/main/java/org/z3950/zing/cql/UnknownRelationException.java similarity index 56% rename from src/org/z3950/zing/cql/UnknownRelationException.java rename to src/main/java/org/z3950/zing/cql/UnknownRelationException.java index 4d65e72..23b3589 100644 --- a/src/org/z3950/zing/cql/UnknownRelationException.java +++ b/src/main/java/org/z3950/zing/cql/UnknownRelationException.java @@ -1,26 +1,22 @@ -// $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 + * 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 UnknownRelationException. + * Creates a new UnknownRelationException. + * * @param s - * The relation for which there was no PQF configuration. + * The relation for which there was no PQF configuration. */ public UnknownRelationException(String s) { - super(s); + super(s); } } diff --git a/src/org/z3950/zing/cql/UnknownRelationModifierException.java b/src/main/java/org/z3950/zing/cql/UnknownRelationModifierException.java similarity index 55% rename from src/org/z3950/zing/cql/UnknownRelationModifierException.java rename to src/main/java/org/z3950/zing/cql/UnknownRelationModifierException.java index 9d449b5..5a31c7c 100644 --- a/src/org/z3950/zing/cql/UnknownRelationModifierException.java +++ b/src/main/java/org/z3950/zing/cql/UnknownRelationModifierException.java @@ -1,26 +1,22 @@ -// $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 + * 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 UnknownRelationModifierException. + * Creates a new UnknownRelationModifierException. + * * @param s - * The relation modifier for which there was no PQF configuration. + * The relation modifier for which there was no PQF configuration. */ public UnknownRelationModifierException(String s) { - super(s); + super(s); } } diff --git a/src/main/java/org/z3950/zing/cql/UnknownTruncationException.java b/src/main/java/org/z3950/zing/cql/UnknownTruncationException.java new file mode 100644 index 0000000..ea65b3a --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/UnknownTruncationException.java @@ -0,0 +1,26 @@ +package org.z3950.zing.cql; + +/** + * Exception indicating that a truncation was not recognised. When rendering a + * tree out as PQF, each term is classified either as left, + * right, left and right truncated, depending on + * whether + * it begins and/or ends with the character *. Its + * classification is looked up as a truncation in the PQF + * configuration. If the truncation is not configured, we throw one of these + * babies. + * + */ +public class UnknownTruncationException extends PQFTranslationException { + private static final long serialVersionUID = 6971993723734811253L; + + /** + * Creates a new UnknownTruncationException. + * + * @param s + * The truncation for which there was no PQF configuration. + */ + public UnknownTruncationException(String s) { + super(s); + } +} diff --git a/src/main/java/org/z3950/zing/cql/XCQLBuilder.java b/src/main/java/org/z3950/zing/cql/XCQLBuilder.java new file mode 100644 index 0000000..c35a280 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/XCQLBuilder.java @@ -0,0 +1,50 @@ +package org.z3950.zing.cql; + +/** + * + * @author jakub + */ +class XCQLBuilder { + private StringBuilder sb; + + XCQLBuilder(StringBuilder sb) { + this.sb = sb; + } + + XCQLBuilder indent(int level) { + while (level-- > 0) { + sb.append(" "); + } + return this; + } + + XCQLBuilder xq(String str) { + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + switch (c) { + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '&': + sb.append("&"); + break; + default: + sb.append(c); + } + } + return this; + } + + XCQLBuilder append(String str) { + sb.append(str); + return this; + } + + @Override + public String toString() { + return sb.toString(); + } +} diff --git a/src/org/z3950/zing/cql/CQLAndNode.java b/src/org/z3950/zing/cql/CQLAndNode.java deleted file mode 100644 index dc452db..0000000 --- a/src/org/z3950/zing/cql/CQLAndNode.java +++ /dev/null @@ -1,29 +0,0 @@ -// $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; - } -} diff --git a/src/org/z3950/zing/cql/CQLBooleanNode.java b/src/org/z3950/zing/cql/CQLBooleanNode.java deleted file mode 100644 index ec0608c..0000000 --- a/src/org/z3950/zing/cql/CQLBooleanNode.java +++ /dev/null @@ -1,90 +0,0 @@ -// $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 prefixes, - Vector sortkeys) { - return (indent(level) + "\n" + - renderPrefixes(level+1, prefixes) + - ms.toXCQL(level+1, "boolean") + - indent(level+1) + "\n" + - left.toXCQL(level+2) + - indent(level+1) + "\n" + - indent(level+1) + "\n" + - right.toXCQL(level+2) + - indent(level+1) + "\n" + - renderSortKeys(level+1, sortkeys) + - indent(level) + "\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(); -} diff --git a/src/org/z3950/zing/cql/CQLGenerator.java b/src/org/z3950/zing/cql/CQLGenerator.java deleted file mode 100644 index 416b771..0000000 --- a/src/org/z3950/zing/cql/CQLGenerator.java +++ /dev/null @@ -1,317 +0,0 @@ -// $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. - *

- * Why is that useful? Mainly to produce test-cases for CQL parsers - * (including the CQLParser 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. - *

- * This code is based on the same grammar as the CQLParser class in - * this distribution - there is a generate_x() method - * for each grammar element X. - * - * @version $Id: CQLGenerator.java,v 1.9 2007-07-03 15:41:35 mike Exp $ - * @see http://zing.z3950.org/cql/index.html - */ -public class CQLGenerator { - Properties params; - Random rnd; - static private boolean DEBUG = false; - - /** - * Creates a new CQL generator with the specified parameters. - *

- * @param params - * A Properties table containing configuration - * parameters for the queries to be generated by this generator. - * Recognised parameters are: - *

- *

- *
seed
- *
- * If specified, this is a long 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 :-) - *

- *

- *
complexQuery
- *
- * [mandatory] A floating-point number between 0.0 and 1.0, - * indicating the probability for each cql-query node - * that it will be expanded into a ``complex query'' - * (cql-query boolean search-clause) rather - * than a search-clause. - *

- *

- *
complexClause
- *
- * [mandatory] A floating-point number between 0.0 and 1.0, - * indicating the probability for each search-clause - * node that it will be expanded into a full sub-query rather - * than an [ index relation ] term triplet. - *

- *

- *
proxOp
- *
- * [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 (and, or and - * not) are chosen with equal probability. - *

- *

- *
equalsRelation
- *
- * [mandatory] A floating-point number between 0.0 and 1.0, - * indicating the probability that each relation will be chosen - * to be = - this is treated as a special case, since - * it's likely to be by far the most common relation in - * ``real life'' searches. - *

- *

- *
numericRelation
- *
- * [mandatory] A floating-point number between 0.0 and 1.0, - * indicating the probability that a relation, having chosen - * not to be =, is instead chosen to be one of the six - * numeric relations (<, >, - * <=, >=, <> and - * =). - *

- *

- *
- */ - 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. - *

- * 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. - *

- * @return - * A CQLNode that is the root of the generated tree. - * That tree may be rendered in XCQL using its toXCQL() - * method, or decompiled into CQL using its toCQL - * 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 ""; - 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. - *

- * It generates a single random query using the parameters - * specified in a nominated properties file, plus any additional - * name value pairs provided on the command-line, and - * decompiles it into CQL which is written to standard output. - *

- * For example, - * java org.z3950.zing.cql.CQLGenerator - * etc/generate.properties seed 18398, - * where the file generate.properties contains:

-     *	complexQuery=0.4
-     *	complexClause=0.4
-     *	equalsRelation=0.5
-     *	numericRelation=0.7
-     *	proxOp=0.0
-     * 
- * yields:
-     *	((dc.author = "<xml.element>") or (bath.title = cat)) and
-     *		(dc.subject >= "the complete dinosaur")
-     * 
- *

- * @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 "+ - "[ ]..."); - 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()); - } -} diff --git a/src/org/z3950/zing/cql/CQLLexer.java b/src/org/z3950/zing/cql/CQLLexer.java deleted file mode 100644 index 5df3822..0000000 --- a/src/org/z3950/zing/cql/CQLLexer.java +++ /dev/null @@ -1,236 +0,0 @@ -// $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 []"); - 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 - } - } -} diff --git a/src/org/z3950/zing/cql/CQLNode.java b/src/org/z3950/zing/cql/CQLNode.java deleted file mode 100644 index 3e42e1e..0000000 --- a/src/org/z3950/zing/cql/CQLNode.java +++ /dev/null @@ -1,354 +0,0 @@ -// $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. - *

- * @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 prefixes) { - return toXCQL(level, prefixes, null); - } - - abstract public String toXCQL(int level, Vector prefixes, - Vector sortkeys); - - protected static String renderPrefixes(int level, Vector prefixes) { - if (prefixes == null || prefixes.size() == 0) - return ""; - String res = indent(level) + "\n"; - for (int i = 0; i < prefixes.size(); i++) { - CQLPrefix p = (CQLPrefix) prefixes.get(i); - res += indent(level+1) + "\n"; - if (p.name != null) - res += indent(level+2) + "" + p.name + "\n"; - res += indent(level+2) + - "" + p.identifier + "\n"; - res += indent(level+1) + "\n"; - } - return res + indent(level) + "\n"; - } - - protected static String renderSortKeys(int level, - Vector sortkeys) { - if (sortkeys == null || sortkeys.size() == 0) - return ""; - String res = indent(level) + "\n"; - for (int i = 0; i < sortkeys.size(); i++) { - ModifierSet key = sortkeys.get(i); - res += key.sortKeyToXCQL(level+1); - } - return res + indent(level) + "\n"; - } - - /** - * Decompiles a parse-tree into a CQL query. - *

- * @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 - * Chapter 7 (Supporting Tools) of the - * YAZ manual. - *

- * @param config - * A Properties 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, - * etc/pqf.properties, 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 < is translated to - * &lt;. - */ - 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 Properties 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, - * etc/pqf.properties, which see. - * @return - * A byte array containing the BER packet. - * @see - * ftp://ftp.rsasecurity.com/pub/pkcs/ascii/layman.asc - */ - 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 madeOIDs = - new java.util.Hashtable(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; - } -} diff --git a/src/org/z3950/zing/cql/CQLParseException.java b/src/org/z3950/zing/cql/CQLParseException.java deleted file mode 100644 index 845dfff..0000000 --- a/src/org/z3950/zing/cql/CQLParseException.java +++ /dev/null @@ -1,23 +0,0 @@ -// $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 CQLParseException. - * @param s - * An error message describing the problem with the query, - * usually a syntax error of some kind. - */ - public CQLParseException(String s) { - super(s); - } -} - diff --git a/src/org/z3950/zing/cql/CQLParser.java b/src/org/z3950/zing/cql/CQLParser.java deleted file mode 100644 index 58e0326..0000000 --- a/src/org/z3950/zing/cql/CQLParser.java +++ /dev/null @@ -1,447 +0,0 @@ -// $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 http://zing.z3950.org/cql/index.html - */ -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 - * compat argument: - *

    - *
  • V1POINT1 - CQL version 1.1 - *
  • - *
  • V1POINT2 - CQL version 1.2 - *
  • - *
  • V1POINT1SORT - CQL version 1.1 but including - * sortby as specified for CQL 1.2. - *
  • - *
- */ - 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. - *

- * 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. toCQL() - * returns a decompiled CQL query equivalent to the one that was - * compiled in the first place; toXCQL() returns an - * XML snippet representing the query; and toPQF() - * 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. - *

- * Reads a CQL query either from its command-line argument, if - * there is one, or standard input otherwise. So these two - * invocations are equivalent: - *

-     *  CQLParser 'au=(Kerninghan or Ritchie) and ti=Unix'
-     *  echo au=(Kerninghan or Ritchie) and ti=Unix | CQLParser
-     * 
- * The test-harness parses the supplied query and renders is as - * XCQL, so that both of the invocations above produce the - * following output: - *
-     *	<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>
-     * 
- *

- * @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 - * -c option is supplied]. - */ - public static void main (String[] args) { - char mode = 'x'; // x=XCQL, c=CQL, p=PQF - String pfile = null; - - Vector argv = new Vector(); - for (int i = 0; i < args.length; i++) { - argv.add(args[i]); - } - - 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 []"); - 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"); - } - } -} diff --git a/src/org/z3950/zing/cql/CQLPrefix.java b/src/org/z3950/zing/cql/CQLPrefix.java deleted file mode 100644 index af7c906..0000000 --- a/src/org/z3950/zing/cql/CQLPrefix.java +++ /dev/null @@ -1,34 +0,0 @@ -// $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 dc, as it might be used in an index - * like dc.title. - */ - 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 http://zthes.z3950.org/cql/1.0. - */ - 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; - } -} diff --git a/src/org/z3950/zing/cql/CQLPrefixNode.java b/src/org/z3950/zing/cql/CQLPrefixNode.java deleted file mode 100644 index dd01d85..0000000 --- a/src/org/z3950/zing/cql/CQLPrefixNode.java +++ /dev/null @@ -1,68 +0,0 @@ -// $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 prefixes, - Vector sortkeys) { - Vector tmp = (prefixes == null ? - new Vector() : - new Vector(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); - } -} diff --git a/src/org/z3950/zing/cql/CQLProxNode.java b/src/org/z3950/zing/cql/CQLProxNode.java deleted file mode 100644 index 2ce355f..0000000 --- a/src/org/z3950/zing/cql/CQLProxNode.java +++ /dev/null @@ -1,127 +0,0 @@ -// $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; - } -} diff --git a/src/org/z3950/zing/cql/CQLRelation.java b/src/org/z3950/zing/cql/CQLRelation.java deleted file mode 100644 index 570afff..0000000 --- a/src/org/z3950/zing/cql/CQLRelation.java +++ /dev/null @@ -1,76 +0,0 @@ -// $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 - * (<=, >, etc.), the text - * relations any, all and exact, the - * old server-choice relation scr and profiled relations of - * the form prefix.name. - */ - // ### 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 relevant, - * fuzzy, stem and phonetic. 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 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"); - } -} diff --git a/src/org/z3950/zing/cql/CQLSortNode.java b/src/org/z3950/zing/cql/CQLSortNode.java deleted file mode 100644 index 6e7f21d..0000000 --- a/src/org/z3950/zing/cql/CQLSortNode.java +++ /dev/null @@ -1,72 +0,0 @@ -// $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 keys; - - public CQLSortNode(CQLNode subtree) { - this.subtree = subtree; - keys = new Vector(); - } - - public void addSortIndex(ModifierSet key) { - keys.add(key); - } - - public Vector getSortIndexes() { - return keys; - } - - public String toXCQL(int level, Vector prefixes, - Vector 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); - } -} diff --git a/src/org/z3950/zing/cql/CQLTermNode.java b/src/org/z3950/zing/cql/CQLTermNode.java deleted file mode 100644 index f9b17ac..0000000 --- a/src/org/z3950/zing/cql/CQLTermNode.java +++ /dev/null @@ -1,280 +0,0 @@ -// $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 index, - * relation and term. The first two may be - * null, but the term 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 prefixes, - Vector sortkeys) { - return (indent(level) + "\n" + - renderPrefixes(level+1, prefixes) + - indent(level+1) + "" + xq(index) + "\n" + - relation.toXCQL(level+1) + - indent(level+1) + "" + xq(term) + "\n" + - renderSortKeys(level+1, sortkeys) + - indent(level) + "\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 attrs = new Vector(); - - // 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 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; - } -} diff --git a/src/org/z3950/zing/cql/Makefile b/src/org/z3950/zing/cql/Makefile deleted file mode 100644 index 39c4f21..0000000 --- a/src/org/z3950/zing/cql/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -# $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' - diff --git a/src/org/z3950/zing/cql/MissingParameterException.java b/src/org/z3950/zing/cql/MissingParameterException.java deleted file mode 100644 index 3b54668..0000000 --- a/src/org/z3950/zing/cql/MissingParameterException.java +++ /dev/null @@ -1,21 +0,0 @@ -// $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 MissingParameterException. - * @param s - * The name of the property whose value was required but not supplied. - */ - public MissingParameterException(String s) { - super(s); - } -} diff --git a/src/org/z3950/zing/cql/Modifier.java b/src/org/z3950/zing/cql/Modifier.java deleted file mode 100644 index 4992b25..0000000 --- a/src/org/z3950/zing/cql/Modifier.java +++ /dev/null @@ -1,87 +0,0 @@ -// $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. - *

- * 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) + "\n"); - buf.append(Utils.indent(level+1) + - "" + Utils.xq(type) + "\n"); - if (value != null) { - buf.append(Utils.indent(level+1) + "<" + relationElement + ">" + - Utils.xq(comparison) + "\n"); - buf.append(Utils.indent(level+1) + - "" + Utils.xq(value) + "\n"); - } - - buf.append(Utils.indent(level) + "\n"); - return buf.toString(); - } - - public String toCQL() { - StringBuffer buf = new StringBuffer(type); - if (value != null) - buf.append(" " + comparison + " " + value); - - return buf.toString(); - } -} diff --git a/src/org/z3950/zing/cql/ModifierSet.java b/src/org/z3950/zing/cql/ModifierSet.java deleted file mode 100644 index 7b6991a..0000000 --- a/src/org/z3950/zing/cql/ModifierSet.java +++ /dev/null @@ -1,128 +0,0 @@ -// $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. - *

- * 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. - *

- * A ModifierSet consists of a ``base'' string together with a set of - * zero or more type comparison value 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 modifiers; - - /** - * Creates a new ModifierSet with the specified base. - */ - public ModifierSet(String base) { - this.base = base; - modifiers = new Vector(); - } - - /** - * Returns the base string with which the ModifierSet was created. - */ - public String getBase() { - return base; - } - - /** - * Adds a modifier of the specified type, - * comparison and value 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 type, but with no - * comparison and value, 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 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) + - "\n"); - if (modifiers.size() > 0) { - buf.append(Utils.indent(level+1) + "\n"); - for (int i = 0; i < modifiers.size(); i++) { - buf.append(modifiers.get(i).toXCQL(level+2, "comparison")); - } - buf.append(Utils.indent(level+1) + "\n"); - } - buf.append(Utils.indent(level) + "\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 [ ]..."); - 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()); - } -} diff --git a/src/org/z3950/zing/cql/UnknownPositionException.java b/src/org/z3950/zing/cql/UnknownPositionException.java deleted file mode 100644 index 5f38ab5..0000000 --- a/src/org/z3950/zing/cql/UnknownPositionException.java +++ /dev/null @@ -1,28 +0,0 @@ -// $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 - * any, first, last or - * firstAndLast, depending on whether it begins and/or ends - * with the word-anchoring meta-character ^. Its - * classification is looked up as a position 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 UnknownPositionException. - * @param s - * The position for which there was no PQF configuration. - */ - public UnknownPositionException(String s) { - super(s); - } -} diff --git a/src/org/z3950/zing/cql/Utils.java b/src/org/z3950/zing/cql/Utils.java deleted file mode 100644 index 6777e46..0000000 --- a/src/org/z3950/zing/cql/Utils.java +++ /dev/null @@ -1,51 +0,0 @@ -// $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; - // 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(); - } -} diff --git a/src/test/java/org/z3950/zing/cql/CQLNodeVisitorTest.java b/src/test/java/org/z3950/zing/cql/CQLNodeVisitorTest.java new file mode 100644 index 0000000..b2da280 --- /dev/null +++ b/src/test/java/org/z3950/zing/cql/CQLNodeVisitorTest.java @@ -0,0 +1,139 @@ +package org.z3950.zing.cql; + +import java.io.IOException; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +import static java.lang.System.*; + +/** + * + * @author jakub + */ +public class CQLNodeVisitorTest { + + public CQLNodeVisitorTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + @Test + public void testPrefixVisitor() { + String cql = "((x=a and y=b) or z=c)"; + String epxected = "@or @and @attr 1=x a @attr 1=y b @attr 1=z c"; + CQLParser p = new CQLParser(CQLParser.V1POINT2); + out.println("Parsing " + cql); + try { + CQLNode n = p.parse(cql); + CQLNodeVisitor v = new CQLDefaultNodeVisitor() { + String actual = ""; + String termSep = ""; + + @Override + public void onBooleanNodeStart(CQLBooleanNode node) { + String op = ""; + switch (node.getOperator()) { + case AND: + op = "@and"; + break; + case OR: + op = "@or"; + break; + } + actual += op + " "; + } + + @Override + public void onTermNode(CQLTermNode node) { + actual += termSep + "@attr 1=" + node.getIndex() + " " + node.getTerm(); + termSep = " "; + } + + @Override + public String toString() { + return actual; + } + }; + n.traverse(v); + out.println(v); + assertEquals(epxected, v.toString()); + } catch (CQLParseException ex) { + fail(ex.getMessage()); + } catch (IOException ex) { + fail(ex.getMessage()); + } + } + + @Test + public void testInfixVisitor() { + String cql = "((x=a and y=b) or z=c)"; + String epxected = "((x:a OG y:b) ELLER z:c)"; + CQLParser p = new CQLParser(CQLParser.V1POINT2); + out.println("Parsing " + cql); + try { + CQLNode n = p.parse(cql); + CQLNodeVisitor v = new CQLDefaultNodeVisitor() { + String actual = ""; + + @Override + public void onBooleanNodeStart(CQLBooleanNode node) { + actual += "("; + } + + @Override + public void onBooleanNodeOp(CQLBooleanNode node) { + String op = ""; + switch (node.getOperator()) { + case AND: + op = "OG"; + break; + case OR: + op = "ELLER"; + break; + } + actual += " " + op + " "; + } + + @Override + public void onBooleanNodeEnd(CQLBooleanNode node) { + actual += ")"; + } + + @Override + public void onTermNode(CQLTermNode node) { + actual += node.getIndex() + ":" + node.getTerm(); + } + + @Override + public String toString() { + return actual; + } + }; + n.traverse(v); + out.println(v); + assertEquals(epxected, v.toString()); + } catch (CQLParseException ex) { + fail(ex.getMessage()); + } catch (IOException ex) { + fail(ex.getMessage()); + } + } +} diff --git a/src/test/java/org/z3950/zing/cql/CQLParserTest.java b/src/test/java/org/z3950/zing/cql/CQLParserTest.java new file mode 100644 index 0000000..5995b5c --- /dev/null +++ b/src/test/java/org/z3950/zing/cql/CQLParserTest.java @@ -0,0 +1,227 @@ +package org.z3950.zing.cql; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.junit.Assert.*; +import static java.lang.System.out; + +/** + * + * @author jakub + */ +public class CQLParserTest { + public CQLParserTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of main method, of class CQLParser. + */ + @Test + public void testRegressionQueries() throws IOException { + System.out.println("Testing the parser using pre-canned regression queries..."); + // we might be running the test from within the jar + // list all resource dirs, then traverse them + String[] dirs = getResourceListing(this.getClass(), "regression"); + for (String dir : dirs) { + String files[] = getResourceListing(this.getClass(), "regression/" + dir); + for (String file : files) { + if (!file.endsWith(".cql")) + continue; + out.println("Parsing " + dir + "/" + file); + InputStream is = this.getClass().getResourceAsStream("/regression/" + dir + "/" + file); + BufferedReader reader = null, reader2 = null; + try { + reader = new BufferedReader(new InputStreamReader(is)); + String input = reader.readLine(); + out.println("Query: " + input); + String result; + try { + CQLParser parser = new CQLParser(); + CQLNode parsed = parser.parse(input); + result = parsed.toXCQL(); + } catch (CQLParseException pe) { + result = pe.getMessage() + "\n"; + } + out.println("Parsed:"); + out.println(result); + // read the expected xcql output + String expected = ""; + String prefix = file.substring(0, file.length() - 4); + InputStream is2 = this.getClass() + .getResourceAsStream("/regression/" + dir + "/" + prefix + ".xcql"); + if (is2 != null) { + reader2 = new BufferedReader(new InputStreamReader(is2)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader2.readLine()) != null) { + sb.append(line).append("\n"); + } + expected = sb.toString(); + } + out.println("Expected: "); + out.println(expected); + assertEquals("Assertion failure for " + dir + "/" + file, expected, result); + } finally { + if (reader != null) + reader.close(); + if (reader2 != null) + reader2.close(); + } + } + } + } + + /** + * Test the integrity of the parser as follows: + * - Generate a random tree with CQLGenerator + * - Serialize it + * - Canonicalise it by running through the parser + * - Compare the before-and-after versions. + * Since the CQLGenerator output is in canonical form anyway, the + * before-and-after versions should be identical. This process exercises + * the comprehensiveness and bullet-proofing of the parser, as well as + * the accuracy of the rendering. + * + * @throws IOException + * @throws MissingParameterException + */ + @Test + public void testRandomQueries() throws IOException, MissingParameterException { + out.println("Testing the parser using 100 randomly generated queries..."); + Properties params = new Properties(); + InputStream is = getClass().getResourceAsStream("/generate.properties"); + if (is == null) + fail("Cannot locate generate.properties"); + params.load(is); + is.close(); + CQLGenerator generator = new CQLGenerator(params); + for (int i = 0; i < 1000; i++) { + CQLNode random = generator.generate(); + String expected = random.toCQL(); + out.println("Generated query: " + expected); + CQLParser parser = new CQLParser(); + try { + CQLNode parsed = parser.parse(expected); + String result = parsed.toCQL(); + assertEquals(expected, result); + } catch (CQLParseException pe) { + fail("Generated query failed to parse: " + pe.getMessage()); + } + } + } + + @SuppressWarnings("rawtypes") + public static String[] getResourceListing(Class clazz, String path) throws IOException { + URL dirURL = clazz.getClassLoader().getResource(path); + if (dirURL != null && dirURL.getProtocol().equals("file")) { + /* A file path: easy enough */ + try { + return new File(dirURL.toURI()).list(); + } catch (URISyntaxException use) { + throw new UnsupportedOperationException(use); + } + } + + if (dirURL == null) { + /* + * In case of a jar file, we can't actually find a directory. + * Have to assume the same jar as clazz. + */ + String me = clazz.getName().replace(".", "/") + ".class"; + dirURL = clazz.getClassLoader().getResource(me); + } + + if (dirURL.getProtocol().equals("jar")) { + /* A JAR path */ + String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf( + "!")); // strip out only the JAR file + try (JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"))) { + Enumeration entries = jar.entries(); // gives ALL entries in jar + Set result = new HashSet(); // avoid duplicates in case it is a subdirectory + while (entries.hasMoreElements()) { + String name = entries.nextElement().getName(); + if (name.startsWith(path)) { // filter according to the path + String entry = name.substring(path.length()); + int checkSubdir = entry.indexOf("/"); + if (checkSubdir >= 0) { + // if it is a subdirectory, we just return the directory name + entry = entry.substring(0, checkSubdir); + } + result.add(entry); + } + } + return result.toArray(new String[result.size()]); + } + } + throw new UnsupportedOperationException("Cannot list files for URL " + + dirURL); + } + + @Test + public void testMakeOID() { + String[] oids = { + "1.2", + "1.2.840.10003.5.109.10", + "2.16.840", + }; + int [][] expected = { + {42}, + {42, 134, 72, 206, 19, 5, 109, 10}, + {96, 134, 72}, + }; + int i = 0; + for (String oid : oids) { + out.println("Testing OID: " + oid); + byte[] encoded = CQLNode.makeOID(oid); + for (int j = 0; j < expected[i].length && j < encoded.length; j++) { + assertEquals("Byte " + j + " of OID " + oid, expected[i][j], encoded[j] & 0xff); + } + assertEquals("Length of OID " + oid, expected[i].length, encoded.length); + i++; + } + // not very consistent below, but this is how it is implemented + assertNull(CQLNode.makeOID("")); + assertNull(CQLNode.makeOID("1")); + assertNull(CQLNode.makeOID("b")); + assertThrows(NumberFormatException.class, () -> { + CQLNode.makeOID("1a."); + }); + assertThrows(NumberFormatException.class, () -> { + CQLNode.makeOID("1.b"); + }); + } +} diff --git a/src/test/java/org/z3950/zing/cql/CQLTermNodeTest.java b/src/test/java/org/z3950/zing/cql/CQLTermNodeTest.java new file mode 100644 index 0000000..760c1fa --- /dev/null +++ b/src/test/java/org/z3950/zing/cql/CQLTermNodeTest.java @@ -0,0 +1,62 @@ +package org.z3950.zing.cql; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class CQLTermNodeTest { + @Test + public void TestTCQLTermQuoteNull() { + assertNull(CQLTermNode.toCQLTerm(null)); + } + + @Test + public void TestTCQLTermQuoteEmpty() { + assertEquals("\"\"", CQLTermNode.toCQLTerm("")); + } + + @Test + public void TestTCQLTermQuoteRelation() { + assertEquals("\"<\"", CQLTermNode.toCQLTerm("<")); + } + + @Test + public void TestTCQLTermQuoteSimple() { + assertEquals("simple", CQLTermNode.toCQLTerm("simple")); + } + + @Test + public void TestTCQLTermQuoteBlank() { + assertEquals("\"a b\"", CQLTermNode.toCQLTerm("a b")); + } + + @Test + public void TestTCQLTermQuoteQuote1() { + assertEquals("a\\\"", CQLTermNode.toCQLTerm("a\"")); + } + + @Test + public void TestTCQLTermQuoteQuote2() { + assertEquals("a\\\"", CQLTermNode.toCQLTerm("a\\\"")); + } + + @Test + public void TestTCQLTermQuoteQuote3() { + assertEquals("a" + "\\\\" + "\\\"", CQLTermNode.toCQLTerm("a" + "\\\\" + "\"")); + } + + @Test + public void TestTCQLTermQuoteQuote4() { + assertEquals("a" + "\\\\" + "\\\"", CQLTermNode.toCQLTerm("a" + "\\\\" + "\\\"")); + } + + @Test + public void TestTCQLTermQuoteBackSlashTrail1() { + assertEquals("a\\\\", CQLTermNode.toCQLTerm("a\\")); + } + + @Test + public void TestTCQLTermQuoteBackSlashTrail2() { + assertEquals("\"a \\\\\"", CQLTermNode.toCQLTerm("a \\")); + } + +} diff --git a/etc/generate.properties b/src/test/resources/generate.properties similarity index 78% rename from etc/generate.properties rename to src/test/resources/generate.properties index 0494623..409f872 100644 --- a/etc/generate.properties +++ b/src/test/resources/generate.properties @@ -1,4 +1,3 @@ -# $Id: generate.properties,v 1.2 2002-11-03 16:49:38 mike Exp $ # # Propeties file to drive the org.z3950.zing.cql.CQLGenerator # test-harness. See that class's documentation for the semantics of diff --git a/src/test/resources/regression/01/01.cql b/src/test/resources/regression/01/01.cql new file mode 100644 index 0000000..5d5b306 --- /dev/null +++ b/src/test/resources/regression/01/01.cql @@ -0,0 +1 @@ +cat \ No newline at end of file diff --git a/test/regression/sections/01/01.xcql b/src/test/resources/regression/01/01.xcql similarity index 100% rename from test/regression/sections/01/01.xcql rename to src/test/resources/regression/01/01.xcql diff --git a/src/test/resources/regression/01/02.cql b/src/test/resources/regression/01/02.cql new file mode 100644 index 0000000..d579091 --- /dev/null +++ b/src/test/resources/regression/01/02.cql @@ -0,0 +1 @@ +"cat" \ No newline at end of file diff --git a/test/regression/sections/01/02.xcql b/src/test/resources/regression/01/02.xcql similarity index 100% rename from test/regression/sections/01/02.xcql rename to src/test/resources/regression/01/02.xcql diff --git a/src/test/resources/regression/01/03.cql b/src/test/resources/regression/01/03.cql new file mode 100644 index 0000000..dac67a1 --- /dev/null +++ b/src/test/resources/regression/01/03.cql @@ -0,0 +1 @@ +comp.os.linux \ No newline at end of file diff --git a/test/regression/sections/01/03.xcql b/src/test/resources/regression/01/03.xcql similarity index 100% rename from test/regression/sections/01/03.xcql rename to src/test/resources/regression/01/03.xcql diff --git a/src/test/resources/regression/01/04.cql b/src/test/resources/regression/01/04.cql new file mode 100644 index 0000000..b665f17 --- /dev/null +++ b/src/test/resources/regression/01/04.cql @@ -0,0 +1 @@ +xml:element \ No newline at end of file diff --git a/test/regression/sections/01/04.xcql b/src/test/resources/regression/01/04.xcql similarity index 100% rename from test/regression/sections/01/04.xcql rename to src/test/resources/regression/01/04.xcql diff --git a/src/test/resources/regression/01/05.cql b/src/test/resources/regression/01/05.cql new file mode 100644 index 0000000..c9b128e --- /dev/null +++ b/src/test/resources/regression/01/05.cql @@ -0,0 +1 @@ +"" \ No newline at end of file diff --git a/test/regression/sections/01/05.xcql b/src/test/resources/regression/01/05.xcql similarity index 100% rename from test/regression/sections/01/05.xcql rename to src/test/resources/regression/01/05.xcql diff --git a/src/test/resources/regression/01/06.cql b/src/test/resources/regression/01/06.cql new file mode 100644 index 0000000..79b87e7 --- /dev/null +++ b/src/test/resources/regression/01/06.cql @@ -0,0 +1 @@ +"=" \ No newline at end of file diff --git a/test/regression/sections/01/06.xcql b/src/test/resources/regression/01/06.xcql similarity index 100% rename from test/regression/sections/01/06.xcql rename to src/test/resources/regression/01/06.xcql diff --git a/src/test/resources/regression/01/07.cql b/src/test/resources/regression/01/07.cql new file mode 100644 index 0000000..34442aa --- /dev/null +++ b/src/test/resources/regression/01/07.cql @@ -0,0 +1 @@ +"prox/distance<3/unit=word" \ No newline at end of file diff --git a/test/regression/sections/01/07.xcql b/src/test/resources/regression/01/07.xcql similarity index 100% rename from test/regression/sections/01/07.xcql rename to src/test/resources/regression/01/07.xcql diff --git a/src/test/resources/regression/01/08.cql b/src/test/resources/regression/01/08.cql new file mode 100644 index 0000000..f6f67a9 --- /dev/null +++ b/src/test/resources/regression/01/08.cql @@ -0,0 +1 @@ +("cat") \ No newline at end of file diff --git a/test/regression/sections/01/08.xcql b/src/test/resources/regression/01/08.xcql similarity index 100% rename from test/regression/sections/01/08.xcql rename to src/test/resources/regression/01/08.xcql diff --git a/src/test/resources/regression/01/09.cql b/src/test/resources/regression/01/09.cql new file mode 100644 index 0000000..ab149fc --- /dev/null +++ b/src/test/resources/regression/01/09.cql @@ -0,0 +1 @@ +((dog)) \ No newline at end of file diff --git a/test/regression/sections/01/09.xcql b/src/test/resources/regression/01/09.xcql similarity index 100% rename from test/regression/sections/01/09.xcql rename to src/test/resources/regression/01/09.xcql diff --git a/src/test/resources/regression/01/10.cql b/src/test/resources/regression/01/10.cql new file mode 100644 index 0000000..baa6044 --- /dev/null +++ b/src/test/resources/regression/01/10.cql @@ -0,0 +1 @@ +all \ No newline at end of file diff --git a/test/regression/sections/01/10.xcql b/src/test/resources/regression/01/10.xcql similarity index 100% rename from test/regression/sections/01/10.xcql rename to src/test/resources/regression/01/10.xcql diff --git a/src/test/resources/regression/01/11.cql b/src/test/resources/regression/01/11.cql new file mode 100644 index 0000000..91c2bee --- /dev/null +++ b/src/test/resources/regression/01/11.cql @@ -0,0 +1 @@ +prox \ No newline at end of file diff --git a/test/regression/sections/01/11.xcql b/src/test/resources/regression/01/11.xcql similarity index 100% rename from test/regression/sections/01/11.xcql rename to src/test/resources/regression/01/11.xcql diff --git a/src/test/resources/regression/01/name b/src/test/resources/regression/01/name new file mode 100644 index 0000000..dc563f2 --- /dev/null +++ b/src/test/resources/regression/01/name @@ -0,0 +1 @@ +Simple \ No newline at end of file diff --git a/src/test/resources/regression/02/01.cql b/src/test/resources/regression/02/01.cql new file mode 100644 index 0000000..fea9bed --- /dev/null +++ b/src/test/resources/regression/02/01.cql @@ -0,0 +1 @@ +title = "fish" \ No newline at end of file diff --git a/test/regression/sections/02/01.xcql b/src/test/resources/regression/02/01.xcql similarity index 100% rename from test/regression/sections/02/01.xcql rename to src/test/resources/regression/02/01.xcql diff --git a/src/test/resources/regression/02/02.cql b/src/test/resources/regression/02/02.cql new file mode 100644 index 0000000..2ae47ba --- /dev/null +++ b/src/test/resources/regression/02/02.cql @@ -0,0 +1 @@ +title == fish diff --git a/src/test/resources/regression/02/02.xcql b/src/test/resources/regression/02/02.xcql new file mode 100644 index 0000000..fd085e3 --- /dev/null +++ b/src/test/resources/regression/02/02.xcql @@ -0,0 +1,7 @@ + + title + + == + + fish + diff --git a/src/test/resources/regression/02/03.cql b/src/test/resources/regression/02/03.cql new file mode 100644 index 0000000..b469c5f --- /dev/null +++ b/src/test/resources/regression/02/03.cql @@ -0,0 +1 @@ +title Any fish diff --git a/src/test/resources/regression/02/03.xcql b/src/test/resources/regression/02/03.xcql new file mode 100644 index 0000000..8034b6a --- /dev/null +++ b/src/test/resources/regression/02/03.xcql @@ -0,0 +1,7 @@ + + title + + Any + + fish + diff --git a/src/test/resources/regression/02/04.cql b/src/test/resources/regression/02/04.cql new file mode 100644 index 0000000..f0f4f19 --- /dev/null +++ b/src/test/resources/regression/02/04.cql @@ -0,0 +1 @@ +title all fish \ No newline at end of file diff --git a/test/regression/sections/02/04.xcql b/src/test/resources/regression/02/04.xcql similarity index 100% rename from test/regression/sections/02/04.xcql rename to src/test/resources/regression/02/04.xcql diff --git a/src/test/resources/regression/02/05.cql b/src/test/resources/regression/02/05.cql new file mode 100644 index 0000000..f20b1df --- /dev/null +++ b/src/test/resources/regression/02/05.cql @@ -0,0 +1 @@ +title > 9 \ No newline at end of file diff --git a/test/regression/sections/02/05.xcql b/src/test/resources/regression/02/05.xcql similarity index 100% rename from test/regression/sections/02/05.xcql rename to src/test/resources/regression/02/05.xcql diff --git a/src/test/resources/regression/02/06.cql b/src/test/resources/regression/02/06.cql new file mode 100644 index 0000000..e081346 --- /dev/null +++ b/src/test/resources/regression/02/06.cql @@ -0,0 +1 @@ +title >= 23 \ No newline at end of file diff --git a/test/regression/sections/02/06.xcql b/src/test/resources/regression/02/06.xcql similarity index 100% rename from test/regression/sections/02/06.xcql rename to src/test/resources/regression/02/06.xcql diff --git a/src/test/resources/regression/02/07.cql b/src/test/resources/regression/02/07.cql new file mode 100644 index 0000000..2c774e0 --- /dev/null +++ b/src/test/resources/regression/02/07.cql @@ -0,0 +1 @@ +dc.title any "fish chips" \ No newline at end of file diff --git a/test/regression/sections/02/07.xcql b/src/test/resources/regression/02/07.xcql similarity index 100% rename from test/regression/sections/02/07.xcql rename to src/test/resources/regression/02/07.xcql diff --git a/src/test/resources/regression/02/08.cql b/src/test/resources/regression/02/08.cql new file mode 100644 index 0000000..acc6a7d --- /dev/null +++ b/src/test/resources/regression/02/08.cql @@ -0,0 +1 @@ +dc.title any/stem fish \ No newline at end of file diff --git a/test/regression/sections/02/08.xcql b/src/test/resources/regression/02/08.xcql similarity index 100% rename from test/regression/sections/02/08.xcql rename to src/test/resources/regression/02/08.xcql diff --git a/src/test/resources/regression/02/09.cql b/src/test/resources/regression/02/09.cql new file mode 100644 index 0000000..fff4f2a --- /dev/null +++ b/src/test/resources/regression/02/09.cql @@ -0,0 +1 @@ +dc.fish all/stem/fuzzy "fish chips" \ No newline at end of file diff --git a/test/regression/sections/02/09.xcql b/src/test/resources/regression/02/09.xcql similarity index 100% rename from test/regression/sections/02/09.xcql rename to src/test/resources/regression/02/09.xcql diff --git a/src/test/resources/regression/02/10.cql b/src/test/resources/regression/02/10.cql new file mode 100644 index 0000000..2723119 --- /dev/null +++ b/src/test/resources/regression/02/10.cql @@ -0,0 +1 @@ +(title any frog) \ No newline at end of file diff --git a/test/regression/sections/02/10.xcql b/src/test/resources/regression/02/10.xcql similarity index 100% rename from test/regression/sections/02/10.xcql rename to src/test/resources/regression/02/10.xcql diff --git a/src/test/resources/regression/02/11.cql b/src/test/resources/regression/02/11.cql new file mode 100644 index 0000000..7c42dc6 --- /dev/null +++ b/src/test/resources/regression/02/11.cql @@ -0,0 +1 @@ +((dc.title any/stem "frog pond")) \ No newline at end of file diff --git a/test/regression/sections/02/11.xcql b/src/test/resources/regression/02/11.xcql similarity index 100% rename from test/regression/sections/02/11.xcql rename to src/test/resources/regression/02/11.xcql diff --git a/src/test/resources/regression/02/12.cql b/src/test/resources/regression/02/12.cql new file mode 100644 index 0000000..dd8106c --- /dev/null +++ b/src/test/resources/regression/02/12.cql @@ -0,0 +1 @@ +dc.title any "fish frog chicken" \ No newline at end of file diff --git a/test/regression/sections/02/12.xcql b/src/test/resources/regression/02/12.xcql similarity index 83% rename from test/regression/sections/02/12.xcql rename to src/test/resources/regression/02/12.xcql index a648cc5..b4bcf3b 100644 --- a/test/regression/sections/02/12.xcql +++ b/src/test/resources/regression/02/12.xcql @@ -1,7 +1,7 @@ dc.title - scr + any fish frog chicken diff --git a/src/test/resources/regression/02/13.cql b/src/test/resources/regression/02/13.cql new file mode 100644 index 0000000..d290d65 --- /dev/null +++ b/src/test/resources/regression/02/13.cql @@ -0,0 +1 @@ +dc.title =/rel.algorithm=CORI squid \ No newline at end of file diff --git a/test/regression/sections/02/13.xcql b/src/test/resources/regression/02/13.xcql similarity index 100% rename from test/regression/sections/02/13.xcql rename to src/test/resources/regression/02/13.xcql diff --git a/src/test/resources/regression/02/14.cql b/src/test/resources/regression/02/14.cql new file mode 100644 index 0000000..58d2b9b --- /dev/null +++ b/src/test/resources/regression/02/14.cql @@ -0,0 +1 @@ +author any/f.foo/b.bar>1 "sanderson taylor" \ No newline at end of file diff --git a/test/regression/sections/02/14.xcql b/src/test/resources/regression/02/14.xcql similarity index 100% rename from test/regression/sections/02/14.xcql rename to src/test/resources/regression/02/14.xcql diff --git a/src/test/resources/regression/02/15.cql b/src/test/resources/regression/02/15.cql new file mode 100644 index 0000000..c427e26 --- /dev/null +++ b/src/test/resources/regression/02/15.cql @@ -0,0 +1 @@ +numberOfLegs <= 4 \ No newline at end of file diff --git a/test/regression/sections/02/15.xcql b/src/test/resources/regression/02/15.xcql similarity index 100% rename from test/regression/sections/02/15.xcql rename to src/test/resources/regression/02/15.xcql diff --git a/src/test/resources/regression/02/16.cql b/src/test/resources/regression/02/16.cql new file mode 100644 index 0000000..eb65cd2 --- /dev/null +++ b/src/test/resources/regression/02/16.cql @@ -0,0 +1 @@ +numberOfLegs <> 4 \ No newline at end of file diff --git a/test/regression/sections/02/16.xcql b/src/test/resources/regression/02/16.xcql similarity index 100% rename from test/regression/sections/02/16.xcql rename to src/test/resources/regression/02/16.xcql diff --git a/src/test/resources/regression/02/17.cql b/src/test/resources/regression/02/17.cql new file mode 100644 index 0000000..2249148 --- /dev/null +++ b/src/test/resources/regression/02/17.cql @@ -0,0 +1 @@ +title == jaws \ No newline at end of file diff --git a/test/regression/sections/02/17.xcql b/src/test/resources/regression/02/17.xcql similarity index 100% rename from test/regression/sections/02/17.xcql rename to src/test/resources/regression/02/17.xcql diff --git a/src/test/resources/regression/02/name b/src/test/resources/regression/02/name new file mode 100644 index 0000000..d9e175e --- /dev/null +++ b/src/test/resources/regression/02/name @@ -0,0 +1 @@ +Index Relation Term \ No newline at end of file diff --git a/src/test/resources/regression/03/01.cql b/src/test/resources/regression/03/01.cql new file mode 100644 index 0000000..a47d816 --- /dev/null +++ b/src/test/resources/regression/03/01.cql @@ -0,0 +1 @@ +cat or dog \ No newline at end of file diff --git a/test/regression/sections/03/01.xcql b/src/test/resources/regression/03/01.xcql similarity index 100% rename from test/regression/sections/03/01.xcql rename to src/test/resources/regression/03/01.xcql diff --git a/src/test/resources/regression/03/02.cql b/src/test/resources/regression/03/02.cql new file mode 100644 index 0000000..7bfe969 --- /dev/null +++ b/src/test/resources/regression/03/02.cql @@ -0,0 +1 @@ +cat and fish \ No newline at end of file diff --git a/test/regression/sections/03/02.xcql b/src/test/resources/regression/03/02.xcql similarity index 100% rename from test/regression/sections/03/02.xcql rename to src/test/resources/regression/03/02.xcql diff --git a/src/test/resources/regression/03/03.cql b/src/test/resources/regression/03/03.cql new file mode 100644 index 0000000..28e500a --- /dev/null +++ b/src/test/resources/regression/03/03.cql @@ -0,0 +1 @@ +cat not frog \ No newline at end of file diff --git a/test/regression/sections/03/03.xcql b/src/test/resources/regression/03/03.xcql similarity index 100% rename from test/regression/sections/03/03.xcql rename to src/test/resources/regression/03/03.xcql diff --git a/src/test/resources/regression/03/04.cql b/src/test/resources/regression/03/04.cql new file mode 100644 index 0000000..eb88011 --- /dev/null +++ b/src/test/resources/regression/03/04.cql @@ -0,0 +1 @@ +(cat not frog) \ No newline at end of file diff --git a/test/regression/sections/03/04.xcql b/src/test/resources/regression/03/04.xcql similarity index 100% rename from test/regression/sections/03/04.xcql rename to src/test/resources/regression/03/04.xcql diff --git a/src/test/resources/regression/03/05.cql b/src/test/resources/regression/03/05.cql new file mode 100644 index 0000000..436176b --- /dev/null +++ b/src/test/resources/regression/03/05.cql @@ -0,0 +1 @@ +"cat" not "fish food" \ No newline at end of file diff --git a/test/regression/sections/03/05.xcql b/src/test/resources/regression/03/05.xcql similarity index 100% rename from test/regression/sections/03/05.xcql rename to src/test/resources/regression/03/05.xcql diff --git a/src/test/resources/regression/03/06.cql b/src/test/resources/regression/03/06.cql new file mode 100644 index 0000000..9c30208 --- /dev/null +++ b/src/test/resources/regression/03/06.cql @@ -0,0 +1 @@ +xml and "prox///" \ No newline at end of file diff --git a/test/regression/sections/03/06.xcql b/src/test/resources/regression/03/06.xcql similarity index 100% rename from test/regression/sections/03/06.xcql rename to src/test/resources/regression/03/06.xcql diff --git a/src/test/resources/regression/03/07.cql b/src/test/resources/regression/03/07.cql new file mode 100644 index 0000000..1c01ad9 --- /dev/null +++ b/src/test/resources/regression/03/07.cql @@ -0,0 +1 @@ +fred and any \ No newline at end of file diff --git a/test/regression/sections/03/07.xcql b/src/test/resources/regression/03/07.xcql similarity index 100% rename from test/regression/sections/03/07.xcql rename to src/test/resources/regression/03/07.xcql diff --git a/src/test/resources/regression/03/08.cql b/src/test/resources/regression/03/08.cql new file mode 100644 index 0000000..f37768b --- /dev/null +++ b/src/test/resources/regression/03/08.cql @@ -0,0 +1 @@ +((fred or all)) \ No newline at end of file diff --git a/test/regression/sections/03/08.xcql b/src/test/resources/regression/03/08.xcql similarity index 100% rename from test/regression/sections/03/08.xcql rename to src/test/resources/regression/03/08.xcql diff --git a/src/test/resources/regression/03/09.cql b/src/test/resources/regression/03/09.cql new file mode 100644 index 0000000..545400b --- /dev/null +++ b/src/test/resources/regression/03/09.cql @@ -0,0 +1 @@ +a or b and c not d \ No newline at end of file diff --git a/test/regression/sections/03/09.xcql b/src/test/resources/regression/03/09.xcql similarity index 100% rename from test/regression/sections/03/09.xcql rename to src/test/resources/regression/03/09.xcql diff --git a/src/test/resources/regression/03/name b/src/test/resources/regression/03/name new file mode 100644 index 0000000..7dd2e3d --- /dev/null +++ b/src/test/resources/regression/03/name @@ -0,0 +1 @@ +Simple Boolean \ No newline at end of file diff --git a/src/test/resources/regression/04/01.cql b/src/test/resources/regression/04/01.cql new file mode 100644 index 0000000..046c5e4 --- /dev/null +++ b/src/test/resources/regression/04/01.cql @@ -0,0 +1 @@ +bath.author any fish and dc.title all "cat dog" \ No newline at end of file diff --git a/test/regression/sections/04/01.xcql b/src/test/resources/regression/04/01.xcql similarity index 100% rename from test/regression/sections/04/01.xcql rename to src/test/resources/regression/04/01.xcql diff --git a/src/test/resources/regression/04/02.cql b/src/test/resources/regression/04/02.cql new file mode 100644 index 0000000..f8372df --- /dev/null +++ b/src/test/resources/regression/04/02.cql @@ -0,0 +1 @@ +(title any/stem "fish dog" or and) \ No newline at end of file diff --git a/test/regression/sections/04/02.xcql b/src/test/resources/regression/04/02.xcql similarity index 100% rename from test/regression/sections/04/02.xcql rename to src/test/resources/regression/04/02.xcql diff --git a/src/test/resources/regression/04/name b/src/test/resources/regression/04/name new file mode 100644 index 0000000..9bafd2c --- /dev/null +++ b/src/test/resources/regression/04/name @@ -0,0 +1 @@ +I/R/T plus Boolean \ No newline at end of file diff --git a/src/test/resources/regression/05/01.cql b/src/test/resources/regression/05/01.cql new file mode 100644 index 0000000..18c09ef --- /dev/null +++ b/src/test/resources/regression/05/01.cql @@ -0,0 +1 @@ +cat prox hat \ No newline at end of file diff --git a/test/regression/sections/05/01.xcql b/src/test/resources/regression/05/01.xcql similarity index 100% rename from test/regression/sections/05/01.xcql rename to src/test/resources/regression/05/01.xcql diff --git a/src/test/resources/regression/05/02.cql b/src/test/resources/regression/05/02.cql new file mode 100644 index 0000000..aa05edd --- /dev/null +++ b/src/test/resources/regression/05/02.cql @@ -0,0 +1 @@ +cat prox/distance=3/unit=word/ordered hat \ No newline at end of file diff --git a/test/regression/sections/05/02.xcql b/src/test/resources/regression/05/02.xcql similarity index 100% rename from test/regression/sections/05/02.xcql rename to src/test/resources/regression/05/02.xcql diff --git a/src/test/resources/regression/05/03.cql b/src/test/resources/regression/05/03.cql new file mode 100644 index 0000000..13a0574 --- /dev/null +++ b/src/test/resources/regression/05/03.cql @@ -0,0 +1 @@ +cat prox/distance<3 hat \ No newline at end of file diff --git a/test/regression/sections/05/03.xcql b/src/test/resources/regression/05/03.xcql similarity index 100% rename from test/regression/sections/05/03.xcql rename to src/test/resources/regression/05/03.xcql diff --git a/src/test/resources/regression/05/04.cql b/src/test/resources/regression/05/04.cql new file mode 100644 index 0000000..53ca6c8 --- /dev/null +++ b/src/test/resources/regression/05/04.cql @@ -0,0 +1 @@ +"fish food" prox/unit=sentence and \ No newline at end of file diff --git a/test/regression/sections/05/04.xcql b/src/test/resources/regression/05/04.xcql similarity index 100% rename from test/regression/sections/05/04.xcql rename to src/test/resources/regression/05/04.xcql diff --git a/src/test/resources/regression/05/05.cql b/src/test/resources/regression/05/05.cql new file mode 100644 index 0000000..18d574d --- /dev/null +++ b/src/test/resources/regression/05/05.cql @@ -0,0 +1 @@ +title all "chips frog" prox/distance<=5 exact \ No newline at end of file diff --git a/test/regression/sections/05/05.xcql b/src/test/resources/regression/05/05.xcql similarity index 100% rename from test/regression/sections/05/05.xcql rename to src/test/resources/regression/05/05.xcql diff --git a/src/test/resources/regression/05/06.cql b/src/test/resources/regression/05/06.cql new file mode 100644 index 0000000..e9ae9fd --- /dev/null +++ b/src/test/resources/regression/05/06.cql @@ -0,0 +1 @@ +(dc.author == "jones" prox/distance>5/unit=element title >= "smith") \ No newline at end of file diff --git a/test/regression/sections/05/06.xcql b/src/test/resources/regression/05/06.xcql similarity index 96% rename from test/regression/sections/05/06.xcql rename to src/test/resources/regression/05/06.xcql index 42029b0..4ffea29 100644 --- a/test/regression/sections/05/06.xcql +++ b/src/test/resources/regression/05/06.xcql @@ -18,7 +18,7 @@ dc.author - exact + == jones diff --git a/src/test/resources/regression/05/07.cql b/src/test/resources/regression/05/07.cql new file mode 100644 index 0000000..fb65e0f --- /dev/null +++ b/src/test/resources/regression/05/07.cql @@ -0,0 +1 @@ +((cat prox hat)) \ No newline at end of file diff --git a/test/regression/sections/05/07.xcql b/src/test/resources/regression/05/07.xcql similarity index 100% rename from test/regression/sections/05/07.xcql rename to src/test/resources/regression/05/07.xcql diff --git a/src/test/resources/regression/05/08.cql b/src/test/resources/regression/05/08.cql new file mode 100644 index 0000000..24a51b2 --- /dev/null +++ b/src/test/resources/regression/05/08.cql @@ -0,0 +1 @@ +a and/rel.SumOfScores b \ No newline at end of file diff --git a/test/regression/sections/05/08.xcql b/src/test/resources/regression/05/08.xcql similarity index 100% rename from test/regression/sections/05/08.xcql rename to src/test/resources/regression/05/08.xcql diff --git a/src/test/resources/regression/05/09.cql b/src/test/resources/regression/05/09.cql new file mode 100644 index 0000000..2cd14ce --- /dev/null +++ b/src/test/resources/regression/05/09.cql @@ -0,0 +1 @@ +a and/rel.algorithm=CORI b \ No newline at end of file diff --git a/test/regression/sections/05/09.xcql b/src/test/resources/regression/05/09.xcql similarity index 100% rename from test/regression/sections/05/09.xcql rename to src/test/resources/regression/05/09.xcql diff --git a/src/test/resources/regression/05/name b/src/test/resources/regression/05/name new file mode 100644 index 0000000..4c6e65c --- /dev/null +++ b/src/test/resources/regression/05/name @@ -0,0 +1 @@ +Prox \ No newline at end of file diff --git a/src/test/resources/regression/06/01.cql b/src/test/resources/regression/06/01.cql new file mode 100644 index 0000000..b428b8f --- /dev/null +++ b/src/test/resources/regression/06/01.cql @@ -0,0 +1 @@ +(cat^) \ No newline at end of file diff --git a/test/regression/sections/06/01.xcql b/src/test/resources/regression/06/01.xcql similarity index 100% rename from test/regression/sections/06/01.xcql rename to src/test/resources/regression/06/01.xcql diff --git a/src/test/resources/regression/06/02.cql b/src/test/resources/regression/06/02.cql new file mode 100644 index 0000000..d579091 --- /dev/null +++ b/src/test/resources/regression/06/02.cql @@ -0,0 +1 @@ +"cat" \ No newline at end of file diff --git a/test/regression/sections/06/02.xcql b/src/test/resources/regression/06/02.xcql similarity index 100% rename from test/regression/sections/06/02.xcql rename to src/test/resources/regression/06/02.xcql diff --git a/src/test/resources/regression/06/03.cql b/src/test/resources/regression/06/03.cql new file mode 100644 index 0000000..d482955 --- /dev/null +++ b/src/test/resources/regression/06/03.cql @@ -0,0 +1 @@ +"^cat says \"fish\"" \ No newline at end of file diff --git a/src/test/resources/regression/06/03.xcql b/src/test/resources/regression/06/03.xcql new file mode 100644 index 0000000..68b2dd5 --- /dev/null +++ b/src/test/resources/regression/06/03.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + ^cat says \"fish\" + diff --git a/src/test/resources/regression/06/04.cql b/src/test/resources/regression/06/04.cql new file mode 100644 index 0000000..ce842d2 --- /dev/null +++ b/src/test/resources/regression/06/04.cql @@ -0,0 +1 @@ +"cat*fish" \ No newline at end of file diff --git a/test/regression/sections/06/04.xcql b/src/test/resources/regression/06/04.xcql similarity index 100% rename from test/regression/sections/06/04.xcql rename to src/test/resources/regression/06/04.xcql diff --git a/src/test/resources/regression/06/05.cql b/src/test/resources/regression/06/05.cql new file mode 100644 index 0000000..21ad338 --- /dev/null +++ b/src/test/resources/regression/06/05.cql @@ -0,0 +1 @@ +cat?dog \ No newline at end of file diff --git a/test/regression/sections/06/05.xcql b/src/test/resources/regression/06/05.xcql similarity index 100% rename from test/regression/sections/06/05.xcql rename to src/test/resources/regression/06/05.xcql diff --git a/src/test/resources/regression/06/06.cql b/src/test/resources/regression/06/06.cql new file mode 100644 index 0000000..0cfced3 --- /dev/null +++ b/src/test/resources/regression/06/06.cql @@ -0,0 +1 @@ +(("^cat*fishdog\"horse?")) \ No newline at end of file diff --git a/src/test/resources/regression/06/06.xcql b/src/test/resources/regression/06/06.xcql new file mode 100644 index 0000000..2a0cd96 --- /dev/null +++ b/src/test/resources/regression/06/06.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + ^cat*fishdog\"horse? + diff --git a/src/test/resources/regression/06/name b/src/test/resources/regression/06/name new file mode 100644 index 0000000..6e12768 --- /dev/null +++ b/src/test/resources/regression/06/name @@ -0,0 +1 @@ +Special Characters \ No newline at end of file diff --git a/src/test/resources/regression/07/01.cql b/src/test/resources/regression/07/01.cql new file mode 100644 index 0000000..2e2ea08 --- /dev/null +++ b/src/test/resources/regression/07/01.cql @@ -0,0 +1 @@ +(((cat or dog) or horse) and frog) \ No newline at end of file diff --git a/test/regression/sections/07/01.xcql b/src/test/resources/regression/07/01.xcql similarity index 100% rename from test/regression/sections/07/01.xcql rename to src/test/resources/regression/07/01.xcql diff --git a/src/test/resources/regression/07/02.cql b/src/test/resources/regression/07/02.cql new file mode 100644 index 0000000..ce2ba87 --- /dev/null +++ b/src/test/resources/regression/07/02.cql @@ -0,0 +1 @@ +(cat and dog) or (horse and frog) \ No newline at end of file diff --git a/test/regression/sections/07/02.xcql b/src/test/resources/regression/07/02.xcql similarity index 100% rename from test/regression/sections/07/02.xcql rename to src/test/resources/regression/07/02.xcql diff --git a/src/test/resources/regression/07/03.cql b/src/test/resources/regression/07/03.cql new file mode 100644 index 0000000..99d3e25 --- /dev/null +++ b/src/test/resources/regression/07/03.cql @@ -0,0 +1 @@ +(cat and (horse or frog)) and chips \ No newline at end of file diff --git a/test/regression/sections/07/03.xcql b/src/test/resources/regression/07/03.xcql similarity index 100% rename from test/regression/sections/07/03.xcql rename to src/test/resources/regression/07/03.xcql diff --git a/src/test/resources/regression/07/name b/src/test/resources/regression/07/name new file mode 100644 index 0000000..b8a913e --- /dev/null +++ b/src/test/resources/regression/07/name @@ -0,0 +1 @@ +Nesting Parentheses \ No newline at end of file diff --git a/src/test/resources/regression/08/01.cql b/src/test/resources/regression/08/01.cql new file mode 100644 index 0000000..665568f --- /dev/null +++ b/src/test/resources/regression/08/01.cql @@ -0,0 +1 @@ +> foo="http://www.loc.gov/zing/cql/dc-indexes/" dc.title="fish" \ No newline at end of file diff --git a/test/regression/sections/08/01.xcql b/src/test/resources/regression/08/01.xcql similarity index 100% rename from test/regression/sections/08/01.xcql rename to src/test/resources/regression/08/01.xcql diff --git a/src/test/resources/regression/08/02.cql b/src/test/resources/regression/08/02.cql new file mode 100644 index 0000000..f5bf9b0 --- /dev/null +++ b/src/test/resources/regression/08/02.cql @@ -0,0 +1 @@ +> "http://www.loc.gov/zing/cql/dc-indexes/" title="fish" \ No newline at end of file diff --git a/test/regression/sections/08/02.xcql b/src/test/resources/regression/08/02.xcql similarity index 100% rename from test/regression/sections/08/02.xcql rename to src/test/resources/regression/08/02.xcql diff --git a/src/test/resources/regression/08/03.cql b/src/test/resources/regression/08/03.cql new file mode 100644 index 0000000..f75c2fc --- /dev/null +++ b/src/test/resources/regression/08/03.cql @@ -0,0 +1 @@ +> foo="http://www.loc.gov/zing/cql/dc-indexes" > ccg = "http://srw.o-r-g.org/cql/indexSets/ccg/" foo.title="fish" and ccg.force=3 \ No newline at end of file diff --git a/test/regression/sections/08/03.xcql b/src/test/resources/regression/08/03.xcql similarity index 100% rename from test/regression/sections/08/03.xcql rename to src/test/resources/regression/08/03.xcql diff --git a/src/test/resources/regression/08/name b/src/test/resources/regression/08/name new file mode 100644 index 0000000..d37a456 --- /dev/null +++ b/src/test/resources/regression/08/name @@ -0,0 +1 @@ +Prefix Maps \ No newline at end of file diff --git a/src/test/resources/regression/09/01.cql b/src/test/resources/regression/09/01.cql new file mode 100644 index 0000000..cda5565 --- /dev/null +++ b/src/test/resources/regression/09/01.cql @@ -0,0 +1 @@ +any or all:stem and all contains any prox proxfuzzy \ No newline at end of file diff --git a/test/regression/sections/09/01.xcql b/src/test/resources/regression/09/01.xcql similarity index 86% rename from test/regression/sections/09/01.xcql rename to src/test/resources/regression/09/01.xcql index d0a5e52..aefe23d 100644 --- a/test/regression/sections/09/01.xcql +++ b/src/test/resources/regression/09/01.xcql @@ -34,22 +34,22 @@ - all + cql.serverChoice - exact + = - any + all contains any - prox + cql.serverChoice = - fuzzy + proxfuzzy diff --git a/src/test/resources/regression/09/02.cql b/src/test/resources/regression/09/02.cql new file mode 100644 index 0000000..3026808 --- /dev/null +++ b/src/test/resources/regression/09/02.cql @@ -0,0 +1 @@ +(((((((((any))))))))) \ No newline at end of file diff --git a/test/regression/sections/09/02.xcql b/src/test/resources/regression/09/02.xcql similarity index 100% rename from test/regression/sections/09/02.xcql rename to src/test/resources/regression/09/02.xcql diff --git a/src/test/resources/regression/09/03.cql b/src/test/resources/regression/09/03.cql new file mode 100644 index 0000000..3cc762b --- /dev/null +++ b/src/test/resources/regression/09/03.cql @@ -0,0 +1 @@ +"" \ No newline at end of file diff --git a/test/regression/sections/09/03.xcql b/src/test/resources/regression/09/03.xcql similarity index 100% rename from test/regression/sections/09/03.xcql rename to src/test/resources/regression/09/03.xcql diff --git a/src/test/resources/regression/09/04.cql b/src/test/resources/regression/09/04.cql new file mode 100644 index 0000000..d2fba11 --- /dev/null +++ b/src/test/resources/regression/09/04.cql @@ -0,0 +1 @@ +> any > any = exact any > any \ No newline at end of file diff --git a/test/regression/sections/09/04.xcql b/src/test/resources/regression/09/04.xcql similarity index 100% rename from test/regression/sections/09/04.xcql rename to src/test/resources/regression/09/04.xcql diff --git a/src/test/resources/regression/09/05.cql b/src/test/resources/regression/09/05.cql new file mode 100644 index 0000000..9fbc491 --- /dev/null +++ b/src/test/resources/regression/09/05.cql @@ -0,0 +1 @@ +sortby sortby sortby \ No newline at end of file diff --git a/test/regression/sections/09/05.xcql b/src/test/resources/regression/09/05.xcql similarity index 100% rename from test/regression/sections/09/05.xcql rename to src/test/resources/regression/09/05.xcql diff --git a/src/test/resources/regression/09/06.cql b/src/test/resources/regression/09/06.cql new file mode 100644 index 0000000..9e80bcb --- /dev/null +++ b/src/test/resources/regression/09/06.cql @@ -0,0 +1 @@ +Sortby Sortby Sortby Sortby Sortby \ No newline at end of file diff --git a/src/test/resources/regression/09/06.xcql b/src/test/resources/regression/09/06.xcql new file mode 100644 index 0000000..337cd16 --- /dev/null +++ b/src/test/resources/regression/09/06.xcql @@ -0,0 +1,18 @@ + + cql.serverChoice + + = + + Sortby + + + Sortby + + + Sortby + + + Sortby + + + diff --git a/src/test/resources/regression/09/name b/src/test/resources/regression/09/name new file mode 100644 index 0000000..ed65cdb --- /dev/null +++ b/src/test/resources/regression/09/name @@ -0,0 +1 @@ +Lame Searches \ No newline at end of file diff --git a/src/test/resources/regression/10/01.cql b/src/test/resources/regression/10/01.cql new file mode 100644 index 0000000..c45d250 --- /dev/null +++ b/src/test/resources/regression/10/01.cql @@ -0,0 +1 @@ +kernighan sortby title \ No newline at end of file diff --git a/test/regression/sections/10/01.xcql b/src/test/resources/regression/10/01.xcql similarity index 100% rename from test/regression/sections/10/01.xcql rename to src/test/resources/regression/10/01.xcql diff --git a/src/test/resources/regression/10/02.cql b/src/test/resources/regression/10/02.cql new file mode 100644 index 0000000..606ba50 --- /dev/null +++ b/src/test/resources/regression/10/02.cql @@ -0,0 +1 @@ +kernighan and ritchie sortby title \ No newline at end of file diff --git a/test/regression/sections/10/02.xcql b/src/test/resources/regression/10/02.xcql similarity index 100% rename from test/regression/sections/10/02.xcql rename to src/test/resources/regression/10/02.xcql diff --git a/src/test/resources/regression/10/03.cql b/src/test/resources/regression/10/03.cql new file mode 100644 index 0000000..beae0f5 --- /dev/null +++ b/src/test/resources/regression/10/03.cql @@ -0,0 +1 @@ +dc.creator=kernighan sortby dc.title \ No newline at end of file diff --git a/test/regression/sections/10/03.xcql b/src/test/resources/regression/10/03.xcql similarity index 100% rename from test/regression/sections/10/03.xcql rename to src/test/resources/regression/10/03.xcql diff --git a/src/test/resources/regression/10/04.cql b/src/test/resources/regression/10/04.cql new file mode 100644 index 0000000..28ef4c4 --- /dev/null +++ b/src/test/resources/regression/10/04.cql @@ -0,0 +1 @@ +dc.creator=kernighan sortby numberOfLegs/cql.number \ No newline at end of file diff --git a/test/regression/sections/10/04.xcql b/src/test/resources/regression/10/04.xcql similarity index 100% rename from test/regression/sections/10/04.xcql rename to src/test/resources/regression/10/04.xcql diff --git a/src/test/resources/regression/10/05.cql b/src/test/resources/regression/10/05.cql new file mode 100644 index 0000000..d263a12 --- /dev/null +++ b/src/test/resources/regression/10/05.cql @@ -0,0 +1 @@ +dc.creator=kernighan sortby dc.title/sort.respectCase \ No newline at end of file diff --git a/test/regression/sections/10/05.xcql b/src/test/resources/regression/10/05.xcql similarity index 100% rename from test/regression/sections/10/05.xcql rename to src/test/resources/regression/10/05.xcql diff --git a/src/test/resources/regression/10/06.cql b/src/test/resources/regression/10/06.cql new file mode 100644 index 0000000..ead9f79 --- /dev/null +++ b/src/test/resources/regression/10/06.cql @@ -0,0 +1 @@ +dc.creator=kernighan sortby dc.title/sort.respectCase/sort.descending \ No newline at end of file diff --git a/test/regression/sections/10/06.xcql b/src/test/resources/regression/10/06.xcql similarity index 100% rename from test/regression/sections/10/06.xcql rename to src/test/resources/regression/10/06.xcql diff --git a/src/test/resources/regression/10/07.cql b/src/test/resources/regression/10/07.cql new file mode 100644 index 0000000..3495ee0 --- /dev/null +++ b/src/test/resources/regression/10/07.cql @@ -0,0 +1 @@ +dc.creator=kernighan sortby dc.date dc.title \ No newline at end of file diff --git a/test/regression/sections/10/07.xcql b/src/test/resources/regression/10/07.xcql similarity index 100% rename from test/regression/sections/10/07.xcql rename to src/test/resources/regression/10/07.xcql diff --git a/src/test/resources/regression/10/08.cql b/src/test/resources/regression/10/08.cql new file mode 100644 index 0000000..27e5a1c --- /dev/null +++ b/src/test/resources/regression/10/08.cql @@ -0,0 +1 @@ +dc.creator=kernighan sortby dc.date/sort.missingOmit \ No newline at end of file diff --git a/test/regression/sections/10/08.xcql b/src/test/resources/regression/10/08.xcql similarity index 100% rename from test/regression/sections/10/08.xcql rename to src/test/resources/regression/10/08.xcql diff --git a/src/test/resources/regression/10/09.cql b/src/test/resources/regression/10/09.cql new file mode 100644 index 0000000..fc41d84 --- /dev/null +++ b/src/test/resources/regression/10/09.cql @@ -0,0 +1 @@ +dc.creator=kernighan sortby dc.date/sort.missingValue=1970 \ No newline at end of file diff --git a/test/regression/sections/10/09.xcql b/src/test/resources/regression/10/09.xcql similarity index 100% rename from test/regression/sections/10/09.xcql rename to src/test/resources/regression/10/09.xcql diff --git a/src/test/resources/regression/10/10.cql b/src/test/resources/regression/10/10.cql new file mode 100644 index 0000000..1f9b830 --- /dev/null +++ b/src/test/resources/regression/10/10.cql @@ -0,0 +1 @@ +>dc="http://deepcustard.org/1.0" blah sortby dc.custardDepth \ No newline at end of file diff --git a/test/regression/sections/10/10.xcql b/src/test/resources/regression/10/10.xcql similarity index 100% rename from test/regression/sections/10/10.xcql rename to src/test/resources/regression/10/10.xcql diff --git a/src/test/resources/regression/10/11.cql b/src/test/resources/regression/10/11.cql new file mode 100644 index 0000000..216ec56 --- /dev/null +++ b/src/test/resources/regression/10/11.cql @@ -0,0 +1 @@ +>ns1="http://uri1" >ns2="http://uri2" whatever sortby ns1.key/a/b/c=1 ns2.key2 \ No newline at end of file diff --git a/test/regression/sections/10/11.xcql b/src/test/resources/regression/10/11.xcql similarity index 100% rename from test/regression/sections/10/11.xcql rename to src/test/resources/regression/10/11.xcql diff --git a/src/test/resources/regression/10/12.cql b/src/test/resources/regression/10/12.cql new file mode 100644 index 0000000..e73b2f7 --- /dev/null +++ b/src/test/resources/regression/10/12.cql @@ -0,0 +1 @@ +(>dc=x b=c) sortby d \ No newline at end of file diff --git a/test/regression/sections/10/12.xcql b/src/test/resources/regression/10/12.xcql similarity index 100% rename from test/regression/sections/10/12.xcql rename to src/test/resources/regression/10/12.xcql diff --git a/src/test/resources/regression/10/13.cql b/src/test/resources/regression/10/13.cql new file mode 100644 index 0000000..60694b0 --- /dev/null +++ b/src/test/resources/regression/10/13.cql @@ -0,0 +1 @@ +b=(>dc=x c) sortby d \ No newline at end of file diff --git a/test/regression/sections/10/13.xcql b/src/test/resources/regression/10/13.xcql similarity index 100% rename from test/regression/sections/10/13.xcql rename to src/test/resources/regression/10/13.xcql diff --git a/src/test/resources/regression/10/14.cql b/src/test/resources/regression/10/14.cql new file mode 100644 index 0000000..5a6f6c5 --- /dev/null +++ b/src/test/resources/regression/10/14.cql @@ -0,0 +1 @@ +(>dc=x c1 and c2) sortby d \ No newline at end of file diff --git a/test/regression/sections/10/14.xcql b/src/test/resources/regression/10/14.xcql similarity index 100% rename from test/regression/sections/10/14.xcql rename to src/test/resources/regression/10/14.xcql diff --git a/src/test/resources/regression/10/15.cql b/src/test/resources/regression/10/15.cql new file mode 100644 index 0000000..9931696 --- /dev/null +++ b/src/test/resources/regression/10/15.cql @@ -0,0 +1 @@ +>dc="http://deepcustard.org" (>dc="http://dublincore.org" dc.title=jaws) sortby dc.custardDepth \ No newline at end of file diff --git a/test/regression/sections/10/15.xcql b/src/test/resources/regression/10/15.xcql similarity index 100% rename from test/regression/sections/10/15.xcql rename to src/test/resources/regression/10/15.xcql diff --git a/src/test/resources/regression/10/16.cql b/src/test/resources/regression/10/16.cql new file mode 100644 index 0000000..eab4136 --- /dev/null +++ b/src/test/resources/regression/10/16.cql @@ -0,0 +1 @@ +>dc="http://deepcustard.org" (fish or >dc="http://dublincore.org" dc.title=jaws) sortby dc.custardDepth \ No newline at end of file diff --git a/test/regression/sections/10/16.xcql b/src/test/resources/regression/10/16.xcql similarity index 100% rename from test/regression/sections/10/16.xcql rename to src/test/resources/regression/10/16.xcql diff --git a/src/test/resources/regression/10/name b/src/test/resources/regression/10/name new file mode 100644 index 0000000..02e6bb6 --- /dev/null +++ b/src/test/resources/regression/10/name @@ -0,0 +1 @@ +Sorting \ No newline at end of file diff --git a/src/test/resources/regression/11/01.cql b/src/test/resources/regression/11/01.cql new file mode 100644 index 0000000..5d56655 --- /dev/null +++ b/src/test/resources/regression/11/01.cql @@ -0,0 +1 @@ +cat or diff --git a/src/test/resources/regression/11/01.xcql b/src/test/resources/regression/11/01.xcql new file mode 100644 index 0000000..03b04df --- /dev/null +++ b/src/test/resources/regression/11/01.xcql @@ -0,0 +1 @@ +expected index or term, got EOF diff --git a/src/test/resources/regression/11/02.cql b/src/test/resources/regression/11/02.cql new file mode 100644 index 0000000..200021d --- /dev/null +++ b/src/test/resources/regression/11/02.cql @@ -0,0 +1 @@ +index any diff --git a/src/test/resources/regression/11/02.xcql b/src/test/resources/regression/11/02.xcql new file mode 100644 index 0000000..03b04df --- /dev/null +++ b/src/test/resources/regression/11/02.xcql @@ -0,0 +1 @@ +expected index or term, got EOF diff --git a/src/test/resources/regression/11/03.cql.disabled b/src/test/resources/regression/11/03.cql.disabled new file mode 100644 index 0000000..daf9170 --- /dev/null +++ b/src/test/resources/regression/11/03.cql.disabled @@ -0,0 +1 @@ +index any/wrong term diff --git a/src/test/resources/regression/11/04.cql.disabled b/src/test/resources/regression/11/04.cql.disabled new file mode 100644 index 0000000..d7a7241 --- /dev/null +++ b/src/test/resources/regression/11/04.cql.disabled @@ -0,0 +1 @@ +a prox/wrong b diff --git a/src/test/resources/regression/11/05.cql b/src/test/resources/regression/11/05.cql new file mode 100644 index 0000000..6a452c1 --- /dev/null +++ b/src/test/resources/regression/11/05.cql @@ -0,0 +1 @@ +() diff --git a/src/test/resources/regression/11/05.xcql b/src/test/resources/regression/11/05.xcql new file mode 100644 index 0000000..9acb4e1 --- /dev/null +++ b/src/test/resources/regression/11/05.xcql @@ -0,0 +1 @@ +expected index or term, got ')' diff --git a/src/test/resources/regression/11/06.cql b/src/test/resources/regression/11/06.cql new file mode 100644 index 0000000..96c10c2 --- /dev/null +++ b/src/test/resources/regression/11/06.cql @@ -0,0 +1 @@ +(a diff --git a/src/test/resources/regression/11/06.xcql b/src/test/resources/regression/11/06.xcql new file mode 100644 index 0000000..1a03f39 --- /dev/null +++ b/src/test/resources/regression/11/06.xcql @@ -0,0 +1 @@ +expected ')', got EOF diff --git a/src/test/resources/regression/11/07.cql b/src/test/resources/regression/11/07.cql new file mode 100644 index 0000000..31a777d --- /dev/null +++ b/src/test/resources/regression/11/07.cql @@ -0,0 +1 @@ +ndex any fish) diff --git a/src/test/resources/regression/11/07.xcql b/src/test/resources/regression/11/07.xcql new file mode 100644 index 0000000..ad3588c --- /dev/null +++ b/src/test/resources/regression/11/07.xcql @@ -0,0 +1 @@ +junk after end: ')' diff --git a/src/test/resources/regression/11/08.cql b/src/test/resources/regression/11/08.cql new file mode 100644 index 0000000..6eaf1dc --- /dev/null +++ b/src/test/resources/regression/11/08.cql @@ -0,0 +1 @@ +(cat any dog or ()) diff --git a/src/test/resources/regression/11/08.xcql b/src/test/resources/regression/11/08.xcql new file mode 100644 index 0000000..9acb4e1 --- /dev/null +++ b/src/test/resources/regression/11/08.xcql @@ -0,0 +1 @@ +expected index or term, got ')' diff --git a/src/test/resources/regression/11/09.cql.disabled b/src/test/resources/regression/11/09.cql.disabled new file mode 100644 index 0000000..ba04d90 --- /dev/null +++ b/src/test/resources/regression/11/09.cql.disabled @@ -0,0 +1 @@ +title = ("illegal parentheses") diff --git a/src/test/resources/regression/11/10.cql.disabled b/src/test/resources/regression/11/10.cql.disabled new file mode 100644 index 0000000..3381e47 --- /dev/null +++ b/src/test/resources/regression/11/10.cql.disabled @@ -0,0 +1 @@ +"quoted" any "illegal quotes" diff --git a/src/test/resources/regression/11/11.cql b/src/test/resources/regression/11/11.cql new file mode 100644 index 0000000..ab692e6 --- /dev/null +++ b/src/test/resources/regression/11/11.cql @@ -0,0 +1 @@ +> illegal="urn:missingQuery" diff --git a/src/test/resources/regression/11/11.xcql b/src/test/resources/regression/11/11.xcql new file mode 100644 index 0000000..03b04df --- /dev/null +++ b/src/test/resources/regression/11/11.xcql @@ -0,0 +1 @@ +expected index or term, got EOF diff --git a/src/test/resources/regression/11/12.cql b/src/test/resources/regression/11/12.cql new file mode 100644 index 0000000..fa804d8 --- /dev/null +++ b/src/test/resources/regression/11/12.cql @@ -0,0 +1 @@ +"fish" and > illegal="urn:invalidPrefixLocation" "chips" diff --git a/src/test/resources/regression/11/12.xcql b/src/test/resources/regression/11/12.xcql new file mode 100644 index 0000000..cfa7186 --- /dev/null +++ b/src/test/resources/regression/11/12.xcql @@ -0,0 +1,29 @@ + + + and + + + + cql.serverChoice + + = + + fish + + + + + + + illegal + urn:invalidPrefixLocation + + + cql.serverChoice + + = + + chips + + + diff --git a/src/test/resources/regression/11/name b/src/test/resources/regression/11/name new file mode 100644 index 0000000..2e6da96 --- /dev/null +++ b/src/test/resources/regression/11/name @@ -0,0 +1 @@ +FAILURES diff --git a/src/test/resources/regression/12/01.cql b/src/test/resources/regression/12/01.cql new file mode 100644 index 0000000..d8ec47d --- /dev/null +++ b/src/test/resources/regression/12/01.cql @@ -0,0 +1 @@ +"te\rm\*\?\^" diff --git a/src/test/resources/regression/12/01.xcql b/src/test/resources/regression/12/01.xcql new file mode 100644 index 0000000..b826cba --- /dev/null +++ b/src/test/resources/regression/12/01.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + te\rm\*\?\^ + diff --git a/src/test/resources/regression/12/02.cql b/src/test/resources/regression/12/02.cql new file mode 100644 index 0000000..e48f360 --- /dev/null +++ b/src/test/resources/regression/12/02.cql @@ -0,0 +1 @@ +te\rm\*\?\^ diff --git a/src/test/resources/regression/12/02.xcql b/src/test/resources/regression/12/02.xcql new file mode 100644 index 0000000..b826cba --- /dev/null +++ b/src/test/resources/regression/12/02.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + te\rm\*\?\^ + diff --git a/src/test/resources/regression/12/name b/src/test/resources/regression/12/name new file mode 100644 index 0000000..09d34d3 --- /dev/null +++ b/src/test/resources/regression/12/name @@ -0,0 +1 @@ +wildcards diff --git a/test/random/Makefile b/test/random/Makefile deleted file mode 100644 index cdb8f4e..0000000 --- a/test/random/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -# $Id: Makefile,v 1.2 2007-06-07 16:16:15 mike Exp $ - -all: - PATH=$(PATH):../../bin CLASSPATH=../../lib/cql-java.jar ./mkrandom 100 - -clean: - @echo "Nothing to do to 'make clean'" - diff --git a/util/random/Makefile b/util/random/Makefile new file mode 100644 index 0000000..d3e1421 --- /dev/null +++ b/util/random/Makefile @@ -0,0 +1,10 @@ + +all: ../../target/cql-java-1.8.jar + PATH=$(PATH):../../bin CLASSPATH=../../target/cql-java-1.8.jar ./mkrandom 100 + +../../target/cql-java-1.8.jar: + cd ../../ && mvn package + +clean: + @echo "Nothing to do to 'make clean'" + diff --git a/test/random/README b/util/random/README similarity index 90% rename from test/random/README rename to util/random/README index 98e7c7f..586796b 100644 --- a/test/random/README +++ b/util/random/README @@ -1,4 +1,3 @@ -$Id: README,v 1.2 2002-12-09 16:29:44 mike Exp $ In this directory, we test the integrity of the CQL-Java tools as follows: diff --git a/test/random/mkrandom b/util/random/mkrandom similarity index 78% rename from test/random/mkrandom rename to util/random/mkrandom index f300534..131e9e7 100755 --- a/test/random/mkrandom +++ b/util/random/mkrandom @@ -1,6 +1,5 @@ #!/usr/bin/perl -w -# $Id: mkrandom,v 1.2 2002-11-03 17:02:48 mike Exp $ use strict; @@ -12,12 +11,17 @@ if (@ARGV > 1) { $n = $ARGV[0]; } +my $nok = 0; for (my $i = 0; $i < $n; $i++) { print $i+1, " of $n -- "; my $query=`CQLGenerator ../../etc/generate.properties`; print $query; my $canon=`CQLParser -c '$query'`; - if ($canon ne $query) { + if ($canon eq $query) { + $nok++; + } else { print "ERROR: canonicalised query differs from original\n"; } } + +print "Passed $nok/$n -- ", int(100*$nok/$n), "%\n"; diff --git a/test/regression/Makefile b/util/regression/Makefile similarity index 94% rename from test/regression/Makefile rename to util/regression/Makefile index 81a3b27..5243e41 100644 --- a/test/regression/Makefile +++ b/util/regression/Makefile @@ -1,4 +1,3 @@ -# $Id: Makefile,v 1.9 2002-11-28 11:56:55 mike Exp $ XMLCANONICALISER = cat # Change this to "./xmlpp.pl" if you want to check for equivalence diff --git a/test/regression/README b/util/regression/README similarity index 98% rename from test/regression/README rename to util/regression/README index ec2b312..0d7b96b 100644 --- a/test/regression/README +++ b/util/regression/README @@ -1,4 +1,3 @@ -$Id: README,v 1.8 2007-06-29 13:06:07 mike Exp $ If you just don't want to think about it ---------------------------------------- diff --git a/test/regression/mkanswers b/util/regression/mkanswers similarity index 94% rename from test/regression/mkanswers rename to util/regression/mkanswers index ca6eefc..16e7dd2 100755 --- a/test/regression/mkanswers +++ b/util/regression/mkanswers @@ -1,6 +1,5 @@ #!/usr/bin/perl -w -# $Id: mkanswers,v 1.5 2002-11-21 09:57:28 mike Exp $ use IO::File; use strict; diff --git a/test/regression/mktests b/util/regression/mktests similarity index 94% rename from test/regression/mktests rename to util/regression/mktests index 41a1500..78057b0 100755 --- a/test/regression/mktests +++ b/util/regression/mktests @@ -1,6 +1,5 @@ #!/usr/bin/perl -w -# $Id: mktests,v 1.3 2002-11-20 22:50:45 mike Exp $ use IO::File; use strict; diff --git a/test/regression/queries.raw b/util/regression/queries.raw similarity index 94% rename from test/regression/queries.raw rename to util/regression/queries.raw index c4104ca..6075e69 100644 --- a/test/regression/queries.raw +++ b/util/regression/queries.raw @@ -26,7 +26,7 @@ dc.title any/stem fish dc.fish all/stem/fuzzy "fish chips" (title any frog) ((dc.title any/stem "frog pond")) -dc.title scr "fish frog chicken" +dc.title any "fish frog chicken" dc.title =/rel.algorithm=CORI squid author any/f.foo/b.bar>1 "sanderson taylor" numberOfLegs <= 4 @@ -57,7 +57,7 @@ cat prox/distance=3/unit=word/ordered hat cat prox/distance<3 hat "fish food" prox/unit=sentence and title all "chips frog" prox/distance<=5 exact -(dc.author exact "jones" prox/distance>5/unit=element title >= "smith") +(dc.author == "jones" prox/distance>5/unit=element title >= "smith") ((cat prox hat)) a and/rel.SumOfScores b a and/rel.algorithm=CORI b @@ -85,7 +85,7 @@ cat?dog # Lame Searches -any or all:stem and all exact any prox prox=fuzzy +any or all:stem and all contains any prox proxfuzzy (((((((((any))))))))) "" > any > any = exact any > any diff --git a/test/regression/runcanon b/util/regression/runcanon similarity index 95% rename from test/regression/runcanon rename to util/regression/runcanon index 9c07e33..46d6a93 100755 --- a/test/regression/runcanon +++ b/util/regression/runcanon @@ -1,6 +1,5 @@ #!/usr/bin/perl -w -# $Id: runcanon,v 1.1 2007-06-29 13:05:12 mike Exp $ # # Tests that all sample queries can be rendered into idempotent # canoncial form. diff --git a/test/regression/runtests b/util/regression/runtests similarity index 96% rename from test/regression/runtests rename to util/regression/runtests index 3c06174..c12cd3c 100755 --- a/test/regression/runtests +++ b/util/regression/runtests @@ -1,10 +1,10 @@ #!/usr/bin/perl -w -# $Id: runtests,v 1.12 2007-07-03 15:53:52 mike Exp $ use IO::File; use strict; +$ENV{CLASSPATH} .= ":../../src/main/java"; $ENV{CLASSPATH} .= ":../../lib/cql-java.jar"; if (@ARGV != 2) { diff --git a/test/regression/sections/01/.cvsignore b/util/regression/sections/01/.gitignore similarity index 100% rename from test/regression/sections/01/.cvsignore rename to util/regression/sections/01/.gitignore diff --git a/util/regression/sections/01/01.xcql b/util/regression/sections/01/01.xcql new file mode 100644 index 0000000..48aee99 --- /dev/null +++ b/util/regression/sections/01/01.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + cat + diff --git a/util/regression/sections/01/02.xcql b/util/regression/sections/01/02.xcql new file mode 100644 index 0000000..48aee99 --- /dev/null +++ b/util/regression/sections/01/02.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + cat + diff --git a/util/regression/sections/01/03.xcql b/util/regression/sections/01/03.xcql new file mode 100644 index 0000000..af191d3 --- /dev/null +++ b/util/regression/sections/01/03.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + comp.os.linux + diff --git a/util/regression/sections/01/04.xcql b/util/regression/sections/01/04.xcql new file mode 100644 index 0000000..4141430 --- /dev/null +++ b/util/regression/sections/01/04.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + xml:element + diff --git a/util/regression/sections/01/05.xcql b/util/regression/sections/01/05.xcql new file mode 100644 index 0000000..6ec410e --- /dev/null +++ b/util/regression/sections/01/05.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + <xml:element> + diff --git a/util/regression/sections/01/06.xcql b/util/regression/sections/01/06.xcql new file mode 100644 index 0000000..da69755 --- /dev/null +++ b/util/regression/sections/01/06.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + = + diff --git a/util/regression/sections/01/07.xcql b/util/regression/sections/01/07.xcql new file mode 100644 index 0000000..851a5c1 --- /dev/null +++ b/util/regression/sections/01/07.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + prox/distance<3/unit=word + diff --git a/util/regression/sections/01/08.xcql b/util/regression/sections/01/08.xcql new file mode 100644 index 0000000..48aee99 --- /dev/null +++ b/util/regression/sections/01/08.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + cat + diff --git a/util/regression/sections/01/09.xcql b/util/regression/sections/01/09.xcql new file mode 100644 index 0000000..d41b39e --- /dev/null +++ b/util/regression/sections/01/09.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + dog + diff --git a/util/regression/sections/01/10.xcql b/util/regression/sections/01/10.xcql new file mode 100644 index 0000000..c5ac7f6 --- /dev/null +++ b/util/regression/sections/01/10.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + all + diff --git a/util/regression/sections/01/11.xcql b/util/regression/sections/01/11.xcql new file mode 100644 index 0000000..88da9eb --- /dev/null +++ b/util/regression/sections/01/11.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + prox + diff --git a/test/regression/sections/02/.cvsignore b/util/regression/sections/02/.gitignore similarity index 100% rename from test/regression/sections/02/.cvsignore rename to util/regression/sections/02/.gitignore diff --git a/util/regression/sections/02/01.xcql b/util/regression/sections/02/01.xcql new file mode 100644 index 0000000..be3c9c1 --- /dev/null +++ b/util/regression/sections/02/01.xcql @@ -0,0 +1,7 @@ + + title + + = + + fish + diff --git a/test/regression/sections/02/02.xcql b/util/regression/sections/02/02.xcql similarity index 100% rename from test/regression/sections/02/02.xcql rename to util/regression/sections/02/02.xcql diff --git a/test/regression/sections/02/03.xcql b/util/regression/sections/02/03.xcql similarity index 100% rename from test/regression/sections/02/03.xcql rename to util/regression/sections/02/03.xcql diff --git a/util/regression/sections/02/04.xcql b/util/regression/sections/02/04.xcql new file mode 100644 index 0000000..646c973 --- /dev/null +++ b/util/regression/sections/02/04.xcql @@ -0,0 +1,7 @@ + + title + + all + + fish + diff --git a/util/regression/sections/02/05.xcql b/util/regression/sections/02/05.xcql new file mode 100644 index 0000000..92411c4 --- /dev/null +++ b/util/regression/sections/02/05.xcql @@ -0,0 +1,7 @@ + + title + + > + + 9 + diff --git a/util/regression/sections/02/06.xcql b/util/regression/sections/02/06.xcql new file mode 100644 index 0000000..6702471 --- /dev/null +++ b/util/regression/sections/02/06.xcql @@ -0,0 +1,7 @@ + + title + + >= + + 23 + diff --git a/util/regression/sections/02/07.xcql b/util/regression/sections/02/07.xcql new file mode 100644 index 0000000..ba4ac78 --- /dev/null +++ b/util/regression/sections/02/07.xcql @@ -0,0 +1,7 @@ + + dc.title + + any + + fish chips + diff --git a/util/regression/sections/02/08.xcql b/util/regression/sections/02/08.xcql new file mode 100644 index 0000000..1576778 --- /dev/null +++ b/util/regression/sections/02/08.xcql @@ -0,0 +1,12 @@ + + dc.title + + any + + + stem + + + + fish + diff --git a/util/regression/sections/02/09.xcql b/util/regression/sections/02/09.xcql new file mode 100644 index 0000000..9f8abff --- /dev/null +++ b/util/regression/sections/02/09.xcql @@ -0,0 +1,15 @@ + + dc.fish + + all + + + stem + + + fuzzy + + + + fish chips + diff --git a/util/regression/sections/02/10.xcql b/util/regression/sections/02/10.xcql new file mode 100644 index 0000000..d7dfe7a --- /dev/null +++ b/util/regression/sections/02/10.xcql @@ -0,0 +1,7 @@ + + title + + any + + frog + diff --git a/util/regression/sections/02/11.xcql b/util/regression/sections/02/11.xcql new file mode 100644 index 0000000..726a36e --- /dev/null +++ b/util/regression/sections/02/11.xcql @@ -0,0 +1,12 @@ + + dc.title + + any + + + stem + + + + frog pond + diff --git a/util/regression/sections/02/12.xcql b/util/regression/sections/02/12.xcql new file mode 100644 index 0000000..b4bcf3b --- /dev/null +++ b/util/regression/sections/02/12.xcql @@ -0,0 +1,7 @@ + + dc.title + + any + + fish frog chicken + diff --git a/util/regression/sections/02/13.xcql b/util/regression/sections/02/13.xcql new file mode 100644 index 0000000..6f3c011 --- /dev/null +++ b/util/regression/sections/02/13.xcql @@ -0,0 +1,14 @@ + + dc.title + + = + + + rel.algorithm + = + CORI + + + + squid + diff --git a/util/regression/sections/02/14.xcql b/util/regression/sections/02/14.xcql new file mode 100644 index 0000000..c833d26 --- /dev/null +++ b/util/regression/sections/02/14.xcql @@ -0,0 +1,17 @@ + + author + + any + + + f.foo + + + b.bar + > + 1 + + + + sanderson taylor + diff --git a/util/regression/sections/02/15.xcql b/util/regression/sections/02/15.xcql new file mode 100644 index 0000000..3a23abe --- /dev/null +++ b/util/regression/sections/02/15.xcql @@ -0,0 +1,7 @@ + + numberOfLegs + + <= + + 4 + diff --git a/util/regression/sections/02/16.xcql b/util/regression/sections/02/16.xcql new file mode 100644 index 0000000..0e8eccd --- /dev/null +++ b/util/regression/sections/02/16.xcql @@ -0,0 +1,7 @@ + + numberOfLegs + + <> + + 4 + diff --git a/util/regression/sections/02/17.xcql b/util/regression/sections/02/17.xcql new file mode 100644 index 0000000..b2587ff --- /dev/null +++ b/util/regression/sections/02/17.xcql @@ -0,0 +1,7 @@ + + title + + == + + jaws + diff --git a/test/regression/sections/03/.cvsignore b/util/regression/sections/03/.gitignore similarity index 100% rename from test/regression/sections/03/.cvsignore rename to util/regression/sections/03/.gitignore diff --git a/util/regression/sections/03/01.xcql b/util/regression/sections/03/01.xcql new file mode 100644 index 0000000..fc1b678 --- /dev/null +++ b/util/regression/sections/03/01.xcql @@ -0,0 +1,23 @@ + + + or + + + + cql.serverChoice + + = + + cat + + + + + cql.serverChoice + + = + + dog + + + diff --git a/util/regression/sections/03/02.xcql b/util/regression/sections/03/02.xcql new file mode 100644 index 0000000..55ae66a --- /dev/null +++ b/util/regression/sections/03/02.xcql @@ -0,0 +1,23 @@ + + + and + + + + cql.serverChoice + + = + + cat + + + + + cql.serverChoice + + = + + fish + + + diff --git a/util/regression/sections/03/03.xcql b/util/regression/sections/03/03.xcql new file mode 100644 index 0000000..2f4f6c0 --- /dev/null +++ b/util/regression/sections/03/03.xcql @@ -0,0 +1,23 @@ + + + not + + + + cql.serverChoice + + = + + cat + + + + + cql.serverChoice + + = + + frog + + + diff --git a/util/regression/sections/03/04.xcql b/util/regression/sections/03/04.xcql new file mode 100644 index 0000000..2f4f6c0 --- /dev/null +++ b/util/regression/sections/03/04.xcql @@ -0,0 +1,23 @@ + + + not + + + + cql.serverChoice + + = + + cat + + + + + cql.serverChoice + + = + + frog + + + diff --git a/util/regression/sections/03/05.xcql b/util/regression/sections/03/05.xcql new file mode 100644 index 0000000..9ad0700 --- /dev/null +++ b/util/regression/sections/03/05.xcql @@ -0,0 +1,23 @@ + + + not + + + + cql.serverChoice + + = + + cat + + + + + cql.serverChoice + + = + + fish food + + + diff --git a/util/regression/sections/03/06.xcql b/util/regression/sections/03/06.xcql new file mode 100644 index 0000000..7b8dae8 --- /dev/null +++ b/util/regression/sections/03/06.xcql @@ -0,0 +1,23 @@ + + + and + + + + cql.serverChoice + + = + + xml + + + + + cql.serverChoice + + = + + prox/// + + + diff --git a/util/regression/sections/03/07.xcql b/util/regression/sections/03/07.xcql new file mode 100644 index 0000000..5e0076e --- /dev/null +++ b/util/regression/sections/03/07.xcql @@ -0,0 +1,23 @@ + + + and + + + + cql.serverChoice + + = + + fred + + + + + cql.serverChoice + + = + + any + + + diff --git a/util/regression/sections/03/08.xcql b/util/regression/sections/03/08.xcql new file mode 100644 index 0000000..f080d4b --- /dev/null +++ b/util/regression/sections/03/08.xcql @@ -0,0 +1,23 @@ + + + or + + + + cql.serverChoice + + = + + fred + + + + + cql.serverChoice + + = + + all + + + diff --git a/util/regression/sections/03/09.xcql b/util/regression/sections/03/09.xcql new file mode 100644 index 0000000..4f3ade7 --- /dev/null +++ b/util/regression/sections/03/09.xcql @@ -0,0 +1,55 @@ + + + not + + + + + and + + + + + or + + + + cql.serverChoice + + = + + a + + + + + cql.serverChoice + + = + + b + + + + + + + cql.serverChoice + + = + + c + + + + + + + cql.serverChoice + + = + + d + + + diff --git a/test/regression/sections/04/.cvsignore b/util/regression/sections/04/.gitignore similarity index 100% rename from test/regression/sections/04/.cvsignore rename to util/regression/sections/04/.gitignore diff --git a/util/regression/sections/04/01.xcql b/util/regression/sections/04/01.xcql new file mode 100644 index 0000000..6f3ec27 --- /dev/null +++ b/util/regression/sections/04/01.xcql @@ -0,0 +1,23 @@ + + + and + + + + bath.author + + any + + fish + + + + + dc.title + + all + + cat dog + + + diff --git a/util/regression/sections/04/02.xcql b/util/regression/sections/04/02.xcql new file mode 100644 index 0000000..0dcb869 --- /dev/null +++ b/util/regression/sections/04/02.xcql @@ -0,0 +1,28 @@ + + + or + + + + title + + any + + + stem + + + + fish dog + + + + + cql.serverChoice + + = + + and + + + diff --git a/test/regression/sections/05/.cvsignore b/util/regression/sections/05/.gitignore similarity index 100% rename from test/regression/sections/05/.cvsignore rename to util/regression/sections/05/.gitignore diff --git a/util/regression/sections/05/01.xcql b/util/regression/sections/05/01.xcql new file mode 100644 index 0000000..406d103 --- /dev/null +++ b/util/regression/sections/05/01.xcql @@ -0,0 +1,23 @@ + + + prox + + + + cql.serverChoice + + = + + cat + + + + + cql.serverChoice + + = + + hat + + + diff --git a/util/regression/sections/05/02.xcql b/util/regression/sections/05/02.xcql new file mode 100644 index 0000000..f09ab0b --- /dev/null +++ b/util/regression/sections/05/02.xcql @@ -0,0 +1,38 @@ + + + prox + + + distance + = + 3 + + + unit + = + word + + + ordered + + + + + + cql.serverChoice + + = + + cat + + + + + cql.serverChoice + + = + + hat + + + diff --git a/util/regression/sections/05/03.xcql b/util/regression/sections/05/03.xcql new file mode 100644 index 0000000..25c5377 --- /dev/null +++ b/util/regression/sections/05/03.xcql @@ -0,0 +1,30 @@ + + + prox + + + distance + < + 3 + + + + + + cql.serverChoice + + = + + cat + + + + + cql.serverChoice + + = + + hat + + + diff --git a/util/regression/sections/05/04.xcql b/util/regression/sections/05/04.xcql new file mode 100644 index 0000000..a6e2198 --- /dev/null +++ b/util/regression/sections/05/04.xcql @@ -0,0 +1,30 @@ + + + prox + + + unit + = + sentence + + + + + + cql.serverChoice + + = + + fish food + + + + + cql.serverChoice + + = + + and + + + diff --git a/util/regression/sections/05/05.xcql b/util/regression/sections/05/05.xcql new file mode 100644 index 0000000..a59341f --- /dev/null +++ b/util/regression/sections/05/05.xcql @@ -0,0 +1,30 @@ + + + prox + + + distance + <= + 5 + + + + + + title + + all + + chips frog + + + + + cql.serverChoice + + = + + exact + + + diff --git a/util/regression/sections/05/06.xcql b/util/regression/sections/05/06.xcql new file mode 100644 index 0000000..4ffea29 --- /dev/null +++ b/util/regression/sections/05/06.xcql @@ -0,0 +1,35 @@ + + + prox + + + distance + > + 5 + + + unit + = + element + + + + + + dc.author + + == + + jones + + + + + title + + >= + + smith + + + diff --git a/util/regression/sections/05/07.xcql b/util/regression/sections/05/07.xcql new file mode 100644 index 0000000..406d103 --- /dev/null +++ b/util/regression/sections/05/07.xcql @@ -0,0 +1,23 @@ + + + prox + + + + cql.serverChoice + + = + + cat + + + + + cql.serverChoice + + = + + hat + + + diff --git a/util/regression/sections/05/08.xcql b/util/regression/sections/05/08.xcql new file mode 100644 index 0000000..b46dcfd --- /dev/null +++ b/util/regression/sections/05/08.xcql @@ -0,0 +1,28 @@ + + + and + + + rel.sumofscores + + + + + + cql.serverChoice + + = + + a + + + + + cql.serverChoice + + = + + b + + + diff --git a/util/regression/sections/05/09.xcql b/util/regression/sections/05/09.xcql new file mode 100644 index 0000000..1dc3602 --- /dev/null +++ b/util/regression/sections/05/09.xcql @@ -0,0 +1,30 @@ + + + and + + + rel.algorithm + = + CORI + + + + + + cql.serverChoice + + = + + a + + + + + cql.serverChoice + + = + + b + + + diff --git a/test/regression/sections/06/.cvsignore b/util/regression/sections/06/.gitignore similarity index 100% rename from test/regression/sections/06/.cvsignore rename to util/regression/sections/06/.gitignore diff --git a/util/regression/sections/06/01.xcql b/util/regression/sections/06/01.xcql new file mode 100644 index 0000000..0dcc5fb --- /dev/null +++ b/util/regression/sections/06/01.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + cat^ + diff --git a/util/regression/sections/06/02.xcql b/util/regression/sections/06/02.xcql new file mode 100644 index 0000000..48aee99 --- /dev/null +++ b/util/regression/sections/06/02.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + cat + diff --git a/test/regression/sections/06/03.xcql b/util/regression/sections/06/03.xcql similarity index 100% rename from test/regression/sections/06/03.xcql rename to util/regression/sections/06/03.xcql diff --git a/util/regression/sections/06/04.xcql b/util/regression/sections/06/04.xcql new file mode 100644 index 0000000..ee38b0e --- /dev/null +++ b/util/regression/sections/06/04.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + cat*fish + diff --git a/util/regression/sections/06/05.xcql b/util/regression/sections/06/05.xcql new file mode 100644 index 0000000..dc59539 --- /dev/null +++ b/util/regression/sections/06/05.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + cat?dog + diff --git a/test/regression/sections/06/06.xcql b/util/regression/sections/06/06.xcql similarity index 100% rename from test/regression/sections/06/06.xcql rename to util/regression/sections/06/06.xcql diff --git a/test/regression/sections/07/.cvsignore b/util/regression/sections/07/.gitignore similarity index 100% rename from test/regression/sections/07/.cvsignore rename to util/regression/sections/07/.gitignore diff --git a/util/regression/sections/07/01.xcql b/util/regression/sections/07/01.xcql new file mode 100644 index 0000000..3338a9f --- /dev/null +++ b/util/regression/sections/07/01.xcql @@ -0,0 +1,55 @@ + + + and + + + + + or + + + + + or + + + + cql.serverChoice + + = + + cat + + + + + cql.serverChoice + + = + + dog + + + + + + + cql.serverChoice + + = + + horse + + + + + + + cql.serverChoice + + = + + frog + + + diff --git a/util/regression/sections/07/02.xcql b/util/regression/sections/07/02.xcql new file mode 100644 index 0000000..1324889 --- /dev/null +++ b/util/regression/sections/07/02.xcql @@ -0,0 +1,55 @@ + + + or + + + + + and + + + + cql.serverChoice + + = + + cat + + + + + cql.serverChoice + + = + + dog + + + + + + + + and + + + + cql.serverChoice + + = + + horse + + + + + cql.serverChoice + + = + + frog + + + + + diff --git a/util/regression/sections/07/03.xcql b/util/regression/sections/07/03.xcql new file mode 100644 index 0000000..202af98 --- /dev/null +++ b/util/regression/sections/07/03.xcql @@ -0,0 +1,55 @@ + + + and + + + + + and + + + + cql.serverChoice + + = + + cat + + + + + + or + + + + cql.serverChoice + + = + + horse + + + + + cql.serverChoice + + = + + frog + + + + + + + + + cql.serverChoice + + = + + chips + + + diff --git a/test/regression/sections/08/.cvsignore b/util/regression/sections/08/.gitignore similarity index 100% rename from test/regression/sections/08/.cvsignore rename to util/regression/sections/08/.gitignore diff --git a/util/regression/sections/08/01.xcql b/util/regression/sections/08/01.xcql new file mode 100644 index 0000000..4e2d548 --- /dev/null +++ b/util/regression/sections/08/01.xcql @@ -0,0 +1,13 @@ + + + + foo + http://www.loc.gov/zing/cql/dc-indexes/ + + + dc.title + + = + + fish + diff --git a/util/regression/sections/08/02.xcql b/util/regression/sections/08/02.xcql new file mode 100644 index 0000000..63ce6ad --- /dev/null +++ b/util/regression/sections/08/02.xcql @@ -0,0 +1,12 @@ + + + + http://www.loc.gov/zing/cql/dc-indexes/ + + + title + + = + + fish + diff --git a/util/regression/sections/08/03.xcql b/util/regression/sections/08/03.xcql new file mode 100644 index 0000000..025c7c9 --- /dev/null +++ b/util/regression/sections/08/03.xcql @@ -0,0 +1,33 @@ + + + + foo + http://www.loc.gov/zing/cql/dc-indexes + + + ccg + http://srw.o-r-g.org/cql/indexSets/ccg/ + + + + and + + + + foo.title + + = + + fish + + + + + ccg.force + + = + + 3 + + + diff --git a/test/regression/sections/09/.cvsignore b/util/regression/sections/09/.gitignore similarity index 100% rename from test/regression/sections/09/.cvsignore rename to util/regression/sections/09/.gitignore diff --git a/util/regression/sections/09/01.xcql b/util/regression/sections/09/01.xcql new file mode 100644 index 0000000..f721ed0 --- /dev/null +++ b/util/regression/sections/09/01.xcql @@ -0,0 +1,39 @@ + + + and + + + + + or + + + + cql.serverChoice + + = + + any + + + + + cql.serverChoice + + = + + all:stem + + + + + + + all contains + + any + + prox proxfuzzy + + + diff --git a/util/regression/sections/09/02.xcql b/util/regression/sections/09/02.xcql new file mode 100644 index 0000000..dd7c8f0 --- /dev/null +++ b/util/regression/sections/09/02.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + any + diff --git a/util/regression/sections/09/03.xcql b/util/regression/sections/09/03.xcql new file mode 100644 index 0000000..1eeff16 --- /dev/null +++ b/util/regression/sections/09/03.xcql @@ -0,0 +1,7 @@ + + cql.serverChoice + + = + + + diff --git a/util/regression/sections/09/04.xcql b/util/regression/sections/09/04.xcql new file mode 100644 index 0000000..aed8eca --- /dev/null +++ b/util/regression/sections/09/04.xcql @@ -0,0 +1,16 @@ + + + + any + + + any + exact + + + any + + > + + any + diff --git a/util/regression/sections/09/05.xcql b/util/regression/sections/09/05.xcql new file mode 100644 index 0000000..578af0f --- /dev/null +++ b/util/regression/sections/09/05.xcql @@ -0,0 +1,12 @@ + + cql.serverChoice + + = + + sortby + + + sortby + + + diff --git a/test/regression/sections/09/06.xcql b/util/regression/sections/09/06.xcql similarity index 100% rename from test/regression/sections/09/06.xcql rename to util/regression/sections/09/06.xcql diff --git a/test/regression/sections/10/.cvsignore b/util/regression/sections/10/.gitignore similarity index 100% rename from test/regression/sections/10/.cvsignore rename to util/regression/sections/10/.gitignore diff --git a/util/regression/sections/10/01.xcql b/util/regression/sections/10/01.xcql new file mode 100644 index 0000000..0c8cc74 --- /dev/null +++ b/util/regression/sections/10/01.xcql @@ -0,0 +1,12 @@ + + cql.serverChoice + + = + + kernighan + + + title + + + diff --git a/util/regression/sections/10/02.xcql b/util/regression/sections/10/02.xcql new file mode 100644 index 0000000..dbd6407 --- /dev/null +++ b/util/regression/sections/10/02.xcql @@ -0,0 +1,28 @@ + + + and + + + + cql.serverChoice + + = + + kernighan + + + + + cql.serverChoice + + = + + ritchie + + + + + title + + + diff --git a/util/regression/sections/10/03.xcql b/util/regression/sections/10/03.xcql new file mode 100644 index 0000000..eaef782 --- /dev/null +++ b/util/regression/sections/10/03.xcql @@ -0,0 +1,12 @@ + + dc.creator + + = + + kernighan + + + dc.title + + + diff --git a/util/regression/sections/10/04.xcql b/util/regression/sections/10/04.xcql new file mode 100644 index 0000000..3a32884 --- /dev/null +++ b/util/regression/sections/10/04.xcql @@ -0,0 +1,17 @@ + + dc.creator + + = + + kernighan + + + numberOfLegs + + + cql.number + + + + + diff --git a/util/regression/sections/10/05.xcql b/util/regression/sections/10/05.xcql new file mode 100644 index 0000000..5821b04 --- /dev/null +++ b/util/regression/sections/10/05.xcql @@ -0,0 +1,17 @@ + + dc.creator + + = + + kernighan + + + dc.title + + + sort.respectcase + + + + + diff --git a/util/regression/sections/10/06.xcql b/util/regression/sections/10/06.xcql new file mode 100644 index 0000000..8d232b2 --- /dev/null +++ b/util/regression/sections/10/06.xcql @@ -0,0 +1,20 @@ + + dc.creator + + = + + kernighan + + + dc.title + + + sort.respectcase + + + sort.descending + + + + + diff --git a/util/regression/sections/10/07.xcql b/util/regression/sections/10/07.xcql new file mode 100644 index 0000000..13737ee --- /dev/null +++ b/util/regression/sections/10/07.xcql @@ -0,0 +1,15 @@ + + dc.creator + + = + + kernighan + + + dc.date + + + dc.title + + + diff --git a/util/regression/sections/10/08.xcql b/util/regression/sections/10/08.xcql new file mode 100644 index 0000000..bcf2a8f --- /dev/null +++ b/util/regression/sections/10/08.xcql @@ -0,0 +1,17 @@ + + dc.creator + + = + + kernighan + + + dc.date + + + sort.missingomit + + + + + diff --git a/util/regression/sections/10/09.xcql b/util/regression/sections/10/09.xcql new file mode 100644 index 0000000..8a60d51 --- /dev/null +++ b/util/regression/sections/10/09.xcql @@ -0,0 +1,19 @@ + + dc.creator + + = + + kernighan + + + dc.date + + + sort.missingvalue + = + 1970 + + + + + diff --git a/util/regression/sections/10/10.xcql b/util/regression/sections/10/10.xcql new file mode 100644 index 0000000..cb97df6 --- /dev/null +++ b/util/regression/sections/10/10.xcql @@ -0,0 +1,18 @@ + + + + dc + http://deepcustard.org/1.0 + + + cql.serverChoice + + = + + blah + + + dc.custardDepth + + + diff --git a/util/regression/sections/10/11.xcql b/util/regression/sections/10/11.xcql new file mode 100644 index 0000000..ba2abe7 --- /dev/null +++ b/util/regression/sections/10/11.xcql @@ -0,0 +1,38 @@ + + + + ns1 + http://uri1 + + + ns2 + http://uri2 + + + cql.serverChoice + + = + + whatever + + + ns1.key + + + a + + + b + + + c + = + 1 + + + + + ns2.key2 + + + diff --git a/util/regression/sections/10/12.xcql b/util/regression/sections/10/12.xcql new file mode 100644 index 0000000..06b0ac2 --- /dev/null +++ b/util/regression/sections/10/12.xcql @@ -0,0 +1,18 @@ + + + + dc + x + + + b + + = + + c + + + d + + + diff --git a/util/regression/sections/10/13.xcql b/util/regression/sections/10/13.xcql new file mode 100644 index 0000000..06b0ac2 --- /dev/null +++ b/util/regression/sections/10/13.xcql @@ -0,0 +1,18 @@ + + + + dc + x + + + b + + = + + c + + + d + + + diff --git a/util/regression/sections/10/14.xcql b/util/regression/sections/10/14.xcql new file mode 100644 index 0000000..4f9f6d2 --- /dev/null +++ b/util/regression/sections/10/14.xcql @@ -0,0 +1,34 @@ + + + + dc + x + + + + and + + + + cql.serverChoice + + = + + c1 + + + + + cql.serverChoice + + = + + c2 + + + + + d + + + diff --git a/util/regression/sections/10/15.xcql b/util/regression/sections/10/15.xcql new file mode 100644 index 0000000..fb278bc --- /dev/null +++ b/util/regression/sections/10/15.xcql @@ -0,0 +1,22 @@ + + + + dc + http://deepcustard.org + + + dc + http://dublincore.org + + + dc.title + + = + + jaws + + + dc.custardDepth + + + diff --git a/util/regression/sections/10/16.xcql b/util/regression/sections/10/16.xcql new file mode 100644 index 0000000..42b01d6 --- /dev/null +++ b/util/regression/sections/10/16.xcql @@ -0,0 +1,40 @@ + + + + dc + http://deepcustard.org + + + + or + + + + cql.serverChoice + + = + + fish + + + + + + + dc + http://dublincore.org + + + dc.title + + = + + jaws + + + + + dc.custardDepth + + + diff --git a/test/regression/showtest b/util/regression/showtest similarity index 86% rename from test/regression/showtest rename to util/regression/showtest index a55c143..0154d9d 100755 --- a/test/regression/showtest +++ b/util/regression/showtest @@ -1,6 +1,5 @@ #!/bin/sh -# $Id: showtest,v 1.6 2002-11-28 11:56:55 mike Exp $ if [ $# != 1 ]; then echo "Usage: $0 " >&2 diff --git a/test/regression/xmlpp.pl b/util/regression/xmlpp.pl similarity index 100% rename from test/regression/xmlpp.pl rename to util/regression/xmlpp.pl