From: mike Date: Wed, 4 Dec 2002 16:54:01 +0000 (+0000) Subject: Temporarily add Ralph's changes to the source, which can generate X-Git-Tag: v1.5~179 X-Git-Url: http://jsfdemo.indexdata.com/cgi-bin?a=commitdiff_plain;h=16021d161561e0bfca678cd879914bd6ae8d8d7d;p=cql-java-moved-to-github.git Temporarily add Ralph's changes to the source, which can generate BER packets. I'll remove these as I integrate them into the master source. --- diff --git a/src/org/z3950/zing/ralph/CQLAndNode.java b/src/org/z3950/zing/ralph/CQLAndNode.java new file mode 100644 index 0000000..d55edf9 --- /dev/null +++ b/src/org/z3950/zing/ralph/CQLAndNode.java @@ -0,0 +1,32 @@ +// $Id: CQLAndNode.java,v 1.1 2002-12-04 16:54:01 mike Exp $ + +package org.z3950.zing.cql; + + +/** + * Represents an AND node in a CQL parse-tree. + * + * @version $Id: CQLAndNode.java,v 1.1 2002-12-04 16:54:01 mike Exp $ + */ +public class CQLAndNode extends CQLBooleanNode { + /** + * Creates a new AND node with the specified left- and right-hand sides. + */ + public CQLAndNode(CQLNode left, CQLNode right) { + this.left = left; + this.right = right; + } + + String op() { + return "and"; + } + + 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/ralph/CQLBooleanNode.java b/src/org/z3950/zing/ralph/CQLBooleanNode.java new file mode 100644 index 0000000..ccd2b3f --- /dev/null +++ b/src/org/z3950/zing/ralph/CQLBooleanNode.java @@ -0,0 +1,82 @@ +// $Id: CQLBooleanNode.java,v 1.1 2002-12-04 16:54:01 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.1 2002-12-04 16:54:01 mike Exp $ + */ +public abstract class CQLBooleanNode extends CQLNode { + CQLBooleanNode() {} // prevent javadoc from documenting this + + /** + * 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; + + public String toXCQL(int level, Vector prefixes) { + return (indent(level) + "\n" + + renderPrefixes(level+1, prefixes) + + opXCQL(level+1) + + indent(level+1) + "\n" + + left.toXCQL(level+2, new Vector()) + + indent(level+1) + "\n" + + indent(level+1) + "\n" + + right.toXCQL(level+2, new Vector()) + + indent(level+1) + "\n" + + indent(level) + "\n"); + } + + // Represents the boolean operation itself: overridden for CQLProxNode + String opXCQL(int level) { + return(indent(level) + "\n" + + indent(level+1) + "" + op() + "\n" + + indent(level) + "\n"); + } + + public String toCQL() { + // ### We don't always need parens around the operands + return "(" + left.toCQL() + ") " + op() + " (" + right.toCQL() + ")"; + } + + public String toPQF(Properties config) throws PQFTranslationException { + return ("@" + opPQF() + + " " + left.toPQF(config) + + " " + right.toPQF(config)); + } + + public byte[] toType1(Properties config) throws PQFTranslationException { + System.out.println("in CQLBooleanNode.toType101(): PQF="+toPQF(config)); + byte[] rpn1=left.toType1(config); + byte[] rpn2=right.toType1(config); + byte[] op=opType1(); + byte[] rpnStructure=new byte[rpn1.length+rpn2.length+op.length+4]; + + int offset=putTag(CONTEXT, 1, CONSTRUCTED, rpnStructure, 0); // rpnRpnOp + 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; + } + + // represents the operation for PQF: overridden for CQLProxNode + String opPQF() { return op(); } + + abstract String op(); + abstract byte[] opType1(); +} diff --git a/src/org/z3950/zing/ralph/CQLNotNode.java b/src/org/z3950/zing/ralph/CQLNotNode.java new file mode 100644 index 0000000..c564509 --- /dev/null +++ b/src/org/z3950/zing/ralph/CQLNotNode.java @@ -0,0 +1,33 @@ +// $Id: CQLNotNode.java,v 1.1 2002-12-04 16:54:01 mike Exp $ + +package org.z3950.zing.cql; + + +/** + * Represents a NOT node in a CQL parse-tree. + * + * @version $Id: CQLNotNode.java,v 1.1 2002-12-04 16:54:01 mike Exp $ + */ +public class CQLNotNode extends CQLBooleanNode { + /** + * Creates a new NOT node with the specified left- and right-hand sides. + */ + public CQLNotNode(CQLNode left, CQLNode right) { + this.left = left; + this.right = right; + } + + String op() { + return "not"; + } + + 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; + } + +} diff --git a/src/org/z3950/zing/ralph/CQLOrNode.java b/src/org/z3950/zing/ralph/CQLOrNode.java new file mode 100644 index 0000000..e304210 --- /dev/null +++ b/src/org/z3950/zing/ralph/CQLOrNode.java @@ -0,0 +1,33 @@ +// $Id: CQLOrNode.java,v 1.1 2002-12-04 16:54:01 mike Exp $ + +package org.z3950.zing.cql; + + +/** + * Represents an OR node in a CQL parse-tree. + * + * @version $Id: CQLOrNode.java,v 1.1 2002-12-04 16:54:01 mike Exp $ + */ +public class CQLOrNode extends CQLBooleanNode { + /** + * Creates a new OR node with the specified left- and right-hand sides. + */ + public CQLOrNode(CQLNode left, CQLNode right) { + this.left = left; + this.right = right; + } + + String op() { + return "or"; + } + + 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; + } + +} diff --git a/src/org/z3950/zing/ralph/CQLPrefix.java b/src/org/z3950/zing/ralph/CQLPrefix.java new file mode 100644 index 0000000..a4bcf7c --- /dev/null +++ b/src/org/z3950/zing/ralph/CQLPrefix.java @@ -0,0 +1,34 @@ +// $Id: CQLPrefix.java,v 1.1 2002-12-04 16:54:01 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.1 2002-12-04 16:54:01 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 a qualifier + * like dc.title. + */ + public String name; + + /** + * The full identifier name of the prefix mapping. That is, + * typically, a URI permanently allocated to a specific qualifier + * 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/ralph/CQLPrefixNode.java b/src/org/z3950/zing/ralph/CQLPrefixNode.java new file mode 100644 index 0000000..525378a --- /dev/null +++ b/src/org/z3950/zing/ralph/CQLPrefixNode.java @@ -0,0 +1,72 @@ +// $Id: CQLPrefixNode.java,v 1.1 2002-12-04 16:54:01 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.1 2002-12-04 16:54:01 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 qualifier-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) { +// String maybeName = ""; +// if (prefix.name != null) +// maybeName = indent(level+1) + "" + prefix.name + "\n"; +// +// return (indent(level) + "\n" + maybeName + +// indent(level+1) + +// "" + prefix.identifier + "\n" + +// subtree.toXCQL(level+1, prefixes) + +// indent(level) + "\n"); + Vector tmp = new Vector(prefixes); + tmp.add(prefix); + return subtree.toXCQL(level, tmp); + } + + public String toCQL() { + // ### We don't always need parens around the operand + 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 qualifiers, + // including their prefixes if any, are instead wired into + // `config'. + return subtree.toPQF(config); + } + + public byte[] toType1(Properties config) throws PQFTranslationException { + // Prefixes and their identifiers don't actually play any role + // in PQF translation, since the meanings of the qualifiers, + // including their prefixes if any, are instead wired into + // `config'. + return subtree.toType1(config); + } +} diff --git a/src/org/z3950/zing/ralph/CQLProxNode.java b/src/org/z3950/zing/ralph/CQLProxNode.java new file mode 100644 index 0000000..b4c6fa8 --- /dev/null +++ b/src/org/z3950/zing/ralph/CQLProxNode.java @@ -0,0 +1,184 @@ +// $Id: CQLProxNode.java,v 1.1 2002-12-04 16:54:01 mike Exp $ + +package org.z3950.zing.cql; +import java.util.Vector; + + +/** + * 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.1 2002-12-04 16:54:01 mike Exp $ + */ +public class CQLProxNode extends CQLBooleanNode { + ModifierSet ms; + + /** + * Creates a new, incomplete, proximity node with the + * specified left-hand side. No right-hand side is specified at + * this stage: that must be specified later, using the + * addSecondSubterm() method. (That may seem odd, but + * it's just easier to write the parser that way.) + *

+ * Proximity paramaters may be added at any time, before or after + * the right-hand-side sub-tree is added. + */ + public CQLProxNode(CQLNode left) { + ms = new ModifierSet("prox"); + this.left = left; + // this.right left unresolved for now ... + } + + /** + * Sets the right-hand side of the proximity node whose + * left-hand-side was specified at creation time. + */ + public void addSecondSubterm(CQLNode right) { + this.right = right; + } + + /** + * Adds a modifier of the specified type and + * value to a proximity node. Valid types are + * relation, distance, unit and + * ordering. + *

+ * For information on the semantics of these paramaters, see + * section 3.1 (Proximity) of + * A Gentle Introduction to CQL. + */ + public void addModifier(String type, String value) { + ms.addModifier(type, value); + } + + /** + * Returns an array of the modifiers associated with a proximity + * node. + * @return + * An array of modifiers, each represented by a two-element + * Vector, in which element 0 is the modifier type + * (e.g. distance or ordering) and element 1 is + * the associated value (e.g. 3 or unordered). + */ + public Vector[] getModifiers() { + return ms.getModifiers(); + } + + String op() { + return ms.toCQL(); + } + + String opXCQL(int level) { + return ms.toXCQL(level, "boolean"); + } + + /* + * 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; // 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/ralph/CQLTermNode.java b/src/org/z3950/zing/ralph/CQLTermNode.java new file mode 100644 index 0000000..8d972ea --- /dev/null +++ b/src/org/z3950/zing/ralph/CQLTermNode.java @@ -0,0 +1,224 @@ +// $Id: CQLTermNode.java,v 1.1 2002-12-04 16:54:01 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, a qualifier string and a relation. Neither or both of + * these must be provided - you can't have a qualifier without a + * relation or vice versa. + * + * @version $Id: CQLTermNode.java,v 1.1 2002-12-04 16:54:01 mike Exp $ + */ +public class CQLTermNode extends CQLNode { + private String qualifier; + private CQLRelation relation; + private String term; + + /** + * Creates a new term node with the specified qualifier, + * relation and term. The first two may be + * null, but the term may not. + */ + public CQLTermNode(String qualifier, CQLRelation relation, String term) { + this.qualifier = qualifier; + this.relation = relation; + this.term = term; + } + + public String getQualifier() { return qualifier; } + public CQLRelation getRelation() { return relation; } + public String getTerm() { return term; } + + public String toXCQL(int level, Vector prefixes) { + return (indent(level) + "\n" + + renderPrefixes(level+1, prefixes) + + indent(level+1) + "" + xq(qualifier) + "\n" + + relation.toXCQL(level+1, new Vector()) + + indent(level+1) + "" + xq(term) + "\n" + + indent(level) + "\n"); + } + + public String toCQL() { + String quotedQualifier = maybeQuote(qualifier); + String quotedTerm = maybeQuote(term); + String res = quotedTerm; + + if (!qualifier.equalsIgnoreCase("srw.serverChoice")) { + // ### We don't always need spaces around `relation'. + res = quotedQualifier + " " + relation.toCQL() + " " + quotedTerm; + } + + return res; + } + + + private Vector getAttrs(Properties config) throws PQFTranslationException { + Vector attrs = new Vector(); + + String attr; + attr = config.getProperty("qualifier." + qualifier); + if (attr == null) + throw new UnknownQualifierException(qualifier); + 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); + + String[] mods = relation.getModifiers(); + for (int i = 0; i < mods.length; i++) { + attr = config.getProperty("relationModifier." + mods[i]); + if (attr == null) + throw new UnknownRelationModifierException(mods[i]); + attrs.add(attr); + } + + String pos = "unanchored"; + String text = term; + if (text.length() > 0 && text.substring(0, 1).equals("^")) { + text = text.substring(1); + pos = "anchored"; + } + 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); + + attr = config.getProperty("always"); + if (attr != null) + attrs.add(attr); + return attrs; + } + + public String toPQF(Properties config) throws PQFTranslationException { + 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); + return s + maybeQuote(text); + } + + static String maybeQuote(String str) { + // 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; + } + + /** 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 qualifiers, + * 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. This may be fed into the tool of + * your choice to obtain a BER-encoded packet. + */ + public byte[] toType1(Properties config) throws PQFTranslationException { + String text = term; + if (text.length() > 0 && text.substring(0, 1).equals("^")) + text = text.substring(1); + String attr, attrList, term=maybeQuote(text); + System.out.println("in CQLTermNode.toType101(): PQF="+toPQF(config)); + 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 + offset=putTag(UNIVERSAL, SEQUENCE, CONSTRUCTED, operand, offset); + operand[offset++]=(byte)(0x80&0xff); + + 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(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; + } + +}