82492270eeb9c95b427a185936d24c91cade5133
[cql-java-moved-to-github.git] / src / org / z3950 / zing / cql / CQLTermNode.java
1 // $Id: CQLTermNode.java,v 1.20 2003-03-04 18:06:11 mike Exp $
2
3 package org.z3950.zing.cql;
4 import java.util.Properties;
5 import java.util.Vector;
6
7
8 /**
9  * Represents a terminal node in a CQL parse-tree.
10  * A term node consists of the term String itself, together with,
11  * optionally, a qualifier string and a relation.  Neither or both of
12  * these must be provided - you can't have a qualifier without a
13  * relation or vice versa.
14  *
15  * @version     $Id: CQLTermNode.java,v 1.20 2003-03-04 18:06:11 mike Exp $
16  */
17 public class CQLTermNode extends CQLNode {
18     private String qualifier;
19     private CQLRelation relation;
20     private String term;
21
22     /**
23      * Creates a new term node with the specified <TT>qualifier</TT>,
24      * <TT>relation</TT> and <TT>term</TT>.  The first two may be
25      * <TT>null</TT>, but the <TT>term</TT> may not.
26      */
27     public CQLTermNode(String qualifier, CQLRelation relation, String term) {
28         this.qualifier = qualifier;
29         this.relation = relation;
30         this.term = term;
31     }
32
33     public String getQualifier() { return qualifier; }
34     public CQLRelation getRelation() { return relation; }
35     public String getTerm() { return term; }
36
37     private static boolean isResultSetQualifier(String qual) {
38         return (qual.equals("srw.resultSet") ||
39                 qual.equals("srw.resultSetId") ||
40                 qual.equals("srw.resultSetName"));
41     }
42
43     public String getResultSetName() {
44         if (isResultSetQualifier(qualifier))
45             return term;
46         else
47             return null;
48     }
49
50     public String toXCQL(int level, Vector prefixes) {
51         return (indent(level) + "<searchClause>\n" +
52                 renderPrefixes(level+1, prefixes) +
53                 indent(level+1) + "<index>" + xq(qualifier) + "</index>\n" +
54                 relation.toXCQL(level+1, new Vector()) +
55                 indent(level+1) + "<term>" + xq(term) + "</term>\n" +
56                 indent(level) + "</searchClause>\n");
57     }
58
59     public String toCQL() {
60         String quotedQualifier = maybeQuote(qualifier);
61         String quotedTerm = maybeQuote(term);
62         String res = quotedTerm;
63
64         if (!qualifier.equalsIgnoreCase("srw.serverChoice")) {
65             // ### We don't always need spaces around `relation'.
66             res = quotedQualifier + " " + relation.toCQL() + " " + quotedTerm;
67         }
68
69         return res;
70     }
71
72     // ### Interaction between this and its callers is not good as
73     //  regards truncation of the term and generation of truncation
74     //  attributes.  Change the interface to fix this.
75     private Vector getAttrs(Properties config) throws PQFTranslationException {
76         Vector attrs = new Vector();
77
78         // Do this first so that if any other truncation or
79         // completeness attributes are generated, they "overwrite"
80         // those specified here.
81         //
82         //  ### This approach relies on an unpleasant detail of Index
83         //      Data's (admittedly definitive) implementation of PQF,
84         //      and should not relied upon.
85         //
86         String attr = config.getProperty("always");
87         if (attr != null)
88             attrs.add(attr);
89
90         attr = config.getProperty("qualifier." + qualifier);
91         if (attr == null)
92             throw new UnknownQualifierException(qualifier);
93         attrs.add(attr);
94
95         String rel = relation.getBase();
96         if (rel.equals("=")) {
97             rel = "eq";
98         } else if (rel.equals("<=")) {
99             rel = "le";
100         } else if (rel.equals(">=")) {
101             rel = "ge";
102         }
103         // ### Handling "any" and "all" properly would involve breaking
104         // the string down into a bunch of individual words and ORring
105         // or ANDing them together.  Another day.
106         attr = config.getProperty("relation." + rel);
107         if (attr == null)
108             throw new UnknownRelationException(rel);
109         attrs.add(attr);
110
111         String[] mods = relation.getModifiers();
112         for (int i = 0; i < mods.length; i++) {
113             attr = config.getProperty("relationModifier." + mods[i]);
114             if (attr == null)
115                 throw new UnknownRelationModifierException(mods[i]);
116             attrs.add(attr);
117         }
118
119         String pos = "any";
120         String text = term;
121         if (text.length() > 0 && text.substring(0, 1).equals("^")) {
122             text = text.substring(1); // ### change not seen by caller
123             pos = "first";
124         }
125         int len = text.length();
126         if (len > 0 && text.substring(len-1, len).equals("^")) {
127             text = text.substring(0, len-1); // ### change not seen by caller
128             pos = pos.equals("first") ? "firstAndLast" : "last";
129             // ### in the firstAndLast case, the standard
130             //  pqf.properties file specifies that we generate a
131             //  completeness=whole-field attributem, which means that
132             //  we don't generate a position attribute at all.  Do we
133             //  care?  Does it matter?
134         }
135
136         attr = config.getProperty("position." + pos);
137         if (attr == null)
138             throw new UnknownPositionException(pos);
139         attrs.add(attr);
140
141         attr = config.getProperty("structure." + rel);
142         if (attr == null)
143             attr = config.getProperty("structure.*");
144         attrs.add(attr);
145
146         return attrs;
147     }
148
149     public String toPQF(Properties config) throws PQFTranslationException {
150         if (isResultSetQualifier(qualifier)) {
151             // Special case: ignore relation, modifiers, wildcards, etc.
152             // There's parallel code in toType1BER()
153             return "@set " + maybeQuote(term);
154         }
155
156         Vector attrs = getAttrs(config);
157
158         String attr, s = "";
159         for (int i = 0; i < attrs.size(); i++) {
160             attr = (String) attrs.get(i);
161             s += "@attr " + Utils.replaceString(attr, " ", " @attr ") + " ";
162         }
163
164         String text = term;
165         if (text.length() > 0 && text.substring(0, 1).equals("^"))
166             text = text.substring(1);
167         int len = text.length();
168         if (len > 0 && text.substring(len-1, len).equals("^"))
169             text = text.substring(0, len-1);
170
171         return s + maybeQuote(text);
172     }
173
174     static String maybeQuote(String str) {
175         // There _must_ be a better way to make this test ...
176         if (str.length() == 0 ||
177             str.indexOf('"') != -1 ||
178             str.indexOf(' ') != -1 ||
179             str.indexOf('\t') != -1 ||
180             str.indexOf('=') != -1 ||
181             str.indexOf('<') != -1 ||
182             str.indexOf('>') != -1 ||
183             str.indexOf('/') != -1 ||
184             str.indexOf('(') != -1 ||
185             str.indexOf(')') != -1) {
186             str = '"' + Utils.replaceString(str, "\"", "\\\"") + '"';
187         }
188
189         return str;
190     }
191
192     public byte[] toType1BER(Properties config) throws PQFTranslationException {
193         if (isResultSetQualifier(qualifier)) {
194             // Special case: ignore relation, modifiers, wildcards, etc.
195             // There's parallel code in toPQF()
196             byte[] operand = new byte[term.length()+100];
197             int offset;
198             offset = putTag(CONTEXT, 0, CONSTRUCTED, operand, 0); // op
199             operand[offset++] = (byte)(0x80&0xff); // indefinite length
200             offset = putTag(CONTEXT, 31, PRIMITIVE, operand, offset); // ResultSetId
201             byte[] t = term.getBytes();
202             offset = putLen(t.length, operand, offset);
203             System.arraycopy(t, 0, operand, offset, t.length);
204             offset += t.length;
205             operand[offset++] = 0x00; // end of Operand
206             operand[offset++] = 0x00;
207             byte[] o = new byte[offset];
208             System.arraycopy(operand, 0, o, 0, offset);
209             return o;
210         }
211
212         String text = term;
213         if (text.length() > 0 && text.substring(0, 1).equals("^"))
214             text = text.substring(1);
215         int len = text.length();
216         if (len > 0 && text.substring(len-1, len).equals("^"))
217             text = text.substring(0, len-1);
218
219         String attr, attrList, term = maybeQuote(text);
220         byte[] operand = new byte[text.length()+100];
221         int i, j, offset, type, value;
222         offset = putTag(CONTEXT, 0, CONSTRUCTED, operand, 0); // op
223         operand[offset++]=(byte)(0x80&0xff); // indefinite length
224         offset = putTag(CONTEXT, 102, CONSTRUCTED, operand, offset); // AttributesPlusTerm
225         operand[offset++] = (byte)(0x80&0xff); // indefinite length
226         offset = putTag(CONTEXT, 44, CONSTRUCTED, operand, offset); // AttributeList
227         operand[offset++] = (byte)(0x80&0xff); // indefinite length
228
229         Vector attrs = getAttrs(config);
230         for(i = 0; i < attrs.size(); i++) {
231             attrList = (String) attrs.get(i);
232             java.util.StringTokenizer st =
233                 new java.util.StringTokenizer(attrList);
234             while (st.hasMoreTokens()) {
235                 attr = st.nextToken();
236                 j = attr.indexOf('=');
237                 offset = putTag(UNIVERSAL, SEQUENCE, CONSTRUCTED, operand, offset);
238                 operand[offset++] = (byte)(0x80&0xff);
239                 offset = putTag(CONTEXT, 120, PRIMITIVE, operand, offset);
240                 type = Integer.parseInt(attr.substring(0, j));
241                 offset = putLen(numLen(type), operand, offset);
242                 offset = putNum(type, operand, offset);
243
244                 offset = putTag(CONTEXT, 121, PRIMITIVE, operand, offset);
245                 value = Integer.parseInt(attr.substring(j+1));
246                 offset = putLen(numLen(value), operand, offset);
247                 offset = putNum(value, operand, offset);
248                 operand[offset++] = 0x00; // end of SEQUENCE
249                 operand[offset++] = 0x00;
250             }
251         }
252         operand[offset++] = 0x00; // end of AttributeList
253         operand[offset++] = 0x00;
254
255         offset = putTag(CONTEXT, 45, PRIMITIVE, operand, offset); // general Term
256         byte[] t = term.getBytes();
257         offset = putLen(t.length, operand, offset);
258         System.arraycopy(t, 0, operand, offset, t.length);
259         offset += t.length;
260
261         operand[offset++] = 0x00; // end of AttributesPlusTerm
262         operand[offset++] = 0x00;
263         operand[offset++] = 0x00; // end of Operand
264         operand[offset++] = 0x00;
265         byte[] o = new byte[offset];
266         System.arraycopy(operand, 0, o, 0, offset);
267         return o;
268     }
269 }