Update CQL parser to use CQL 1.1 modifiers for booleans and
[yaz-moved-to-github.git] / src / cqltransform.c
1 /* $Id: cqltransform.c,v 1.7 2004-03-10 16:34:29 adam Exp $
2    Copyright (C) 2002-2003
3    Index Data Aps
4
5 This file is part of the YAZ toolkit.
6
7 See the file LICENSE.
8 */
9
10 #include <stdlib.h>
11 #include <string.h>
12 #include <yaz/cql.h>
13
14 struct cql_prop_entry {
15     char *pattern;
16     char *value;
17     struct cql_prop_entry *next;
18 };
19
20 struct cql_transform_t_ {
21     struct cql_prop_entry *entry;
22     int error;
23     char *addinfo;
24 };
25
26 cql_transform_t cql_transform_open_FILE(FILE *f)
27 {
28     char line[1024];
29     cql_transform_t ct = (cql_transform_t) malloc (sizeof(*ct));
30     struct cql_prop_entry **pp = &ct->entry;
31
32     ct->error = 0;
33     ct->addinfo = 0;
34     while (fgets(line, sizeof(line)-1, f))
35     {
36         const char *cp_value_start;
37         const char *cp_value_end;
38         const char *cp_pattern_end;
39         const char *cp = line;
40         while (*cp && !strchr(" \t=\r\n#", *cp))
41             cp++;
42         cp_pattern_end = cp;
43         if (cp == line)
44             continue;
45         while (*cp && strchr(" \t\r\n", *cp))
46             cp++;
47         if (*cp != '=')
48             continue;
49         cp++;
50         while (*cp && strchr(" \t\r\n", *cp))
51             cp++;
52         cp_value_start = cp;
53         if (!(cp_value_end = strchr(cp, '#')))
54             cp_value_end = strlen(line) + line;
55
56         if (cp_value_end != cp_value_start &&
57             strchr(" \t\r\n", cp_value_end[-1]))
58             cp_value_end--;
59         *pp = (struct cql_prop_entry *) malloc (sizeof(**pp));
60         (*pp)->pattern = (char *) malloc (cp_pattern_end - line + 1);
61         memcpy ((*pp)->pattern, line, cp_pattern_end - line);
62         (*pp)->pattern[cp_pattern_end-line] = 0;
63
64         (*pp)->value = (char *) malloc (cp_value_end - cp_value_start + 1);
65         if (cp_value_start != cp_value_end)
66             memcpy ((*pp)->value, cp_value_start, cp_value_end-cp_value_start);
67         (*pp)->value[cp_value_end - cp_value_start] = 0;
68         pp = &(*pp)->next;
69     }
70     *pp = 0;
71     return ct;
72 }
73
74 void cql_transform_close(cql_transform_t ct)
75 {
76     struct cql_prop_entry *pe;
77     if (!ct)
78         return;
79     pe = ct->entry;
80     while (pe)
81     {
82         struct cql_prop_entry *pe_next = pe->next;
83         free (pe->pattern);
84         free (pe->value);
85         free (pe);
86         pe = pe_next;
87     }
88     if (ct->addinfo)
89         free (ct->addinfo);
90     free (ct);
91 }
92
93 cql_transform_t cql_transform_open_fname(const char *fname)
94 {
95     cql_transform_t ct;
96     FILE *f = fopen(fname, "r");
97     if (!f)
98         return 0;
99     ct = cql_transform_open_FILE(f);
100     fclose(f);
101     return ct;
102 }
103
104 static const char *cql_lookup_property(cql_transform_t ct,
105                                        const char *pat1, const char *pat2)
106 {
107     char pattern[80];
108     struct cql_prop_entry *e;
109
110     if (pat2)
111         sprintf (pattern, "%.39s%.39s", pat1, pat2);
112     else
113         sprintf (pattern, "%.39s", pat1);
114     for (e = ct->entry; e; e = e->next)
115     {
116         if (!strcmp(e->pattern, pattern))
117             return e->value;
118     }
119     return 0;
120 }
121
122 static const char *cql_lookup_value(cql_transform_t ct,
123                                     const char *prefix,
124                                     const char *value)
125 {
126     struct cql_prop_entry *e;
127     int len = strlen(prefix);
128
129     for (e = ct->entry; e; e = e->next)
130     {
131         if (!memcmp(e->pattern, prefix, len) && !strcmp(e->value, value))
132             return e->pattern + len;
133     }
134     return 0;
135 }
136
137
138 int cql_pr_attr(cql_transform_t ct, const char *category,
139                 const char *val,
140                 const char *default_val,
141                 void (*pr)(const char *buf, void *client_data),
142                 void *client_data,
143                 int errcode)
144 {
145     const char *res;
146     res = cql_lookup_property(ct, category, val ? val : default_val);
147     if (!res)
148         res = cql_lookup_property(ct, category, "*");
149     if (res)
150     {
151         char buf[64];
152
153         const char *cp0 = res, *cp1;
154         while ((cp1 = strchr(cp0, '=')))
155         {
156             while (*cp1 && *cp1 != ' ')
157                 cp1++;
158             if (cp1 - cp0 >= sizeof(buf))
159                 break;
160             memcpy (buf, cp0, cp1 - cp0);
161             buf[cp1-cp0] = 0;
162             (*pr)("@attr ", client_data);
163             (*pr)(buf, client_data);
164             (*pr)(" ", client_data);
165             cp0 = cp1;
166             while (*cp0 == ' ')
167                 cp0++;
168         }
169         return 1;
170     }
171     /* error ... */
172     if (errcode && !ct->error)
173     {
174         ct->error = errcode;
175         if (val)
176             ct->addinfo = strdup(val);
177         else
178             ct->addinfo = 0;
179     }
180     return 0;
181 }
182
183
184 /* Returns location of first wildcard character in the `length'
185  * characters starting at `term', or a null pointer of there are
186  * none -- like memchr().
187  */
188 static char *wcchar(const char *term, int length)
189 {
190     char *best = 0;
191     char *current;
192     char *whichp;
193
194     for (whichp = "*?"; *whichp != '\0'; whichp++) {
195         current = memchr(term, *whichp, length);
196         if (current != 0 && (best == 0 || current < best))
197             best = current;
198     }
199
200     return best;
201 }
202
203
204 void emit_term(cql_transform_t ct,
205                const char *term, int length,
206                void (*pr)(const char *buf, void *client_data),
207                void *client_data)
208 {
209     int i;
210     if (length > 0)
211     {
212         if (length > 1 && term[0] == '^' && term[length-1] == '^')
213         {
214             cql_pr_attr(ct, "position.", "firstAndLast", 0,
215                         pr, client_data, 32);
216             term++;
217             length -= 2;
218         }
219         else if (term[0] == '^')
220         {
221             cql_pr_attr(ct, "position.", "first", 0,
222                         pr, client_data, 32);
223             term++;
224             length--;
225         }
226         else if (term[length-1] == '^')
227         {
228             cql_pr_attr(ct, "position.", "last", 0,
229                         pr, client_data, 32);
230             length--;
231         }
232         else
233         {
234             cql_pr_attr(ct, "position.", "any", 0,
235                         pr, client_data, 32);
236         }
237     }
238
239     if (length > 0)
240     {
241         /* Check for well-known globbing patterns that represent
242          * simple truncation attributes as expected by, for example,
243          * Bath-compliant server.  If we find such a pattern but
244          * there's no mapping for it, that's fine: we just use a
245          * general pattern-matching attribute.
246          */
247         if (length > 1 && term[0] == '*' && term[length-1] == '*' &&
248             wcchar(term+1, length-2) == 0 &&
249             cql_pr_attr(ct, "truncation.", "both", 0,
250                         pr, client_data, 0)) {
251             term++;
252             length -= 2;
253         }
254         else if (term[0] == '*' &&
255                  wcchar(term+1, length-1) == 0 &&
256                  cql_pr_attr(ct, "truncation.", "left", 0,
257                              pr, client_data, 0)) {
258             term++;
259             length--;
260         }
261         else if (term[length-1] == '*' &&
262                  wcchar(term, length-1) == 0 &&
263                  cql_pr_attr(ct, "truncation.", "right", 0,
264                              pr, client_data, 0)) {
265             length--;
266         }
267         else if (wcchar(term, length))
268         {
269             /* We have one or more wildcard characters, but not in a
270              * way that can be dealt with using only the standard
271              * left-, right- and both-truncation attributes.  We need
272              * to translate the pattern into a Z39.58-type pattern,
273              * which has been supported in BIB-1 since 1996.  If
274              * there's no configuration element for "truncation.z3958"
275              * we indicate this as error 28 "Masking character not
276              * supported".
277              */
278             int i;
279             char *mem;
280             cql_pr_attr(ct, "truncation.", "z3958", 0,
281                         pr, client_data, 28);
282             mem = malloc(length+1);
283             for (i = 0; i < length; i++) {
284                 if (term[i] == '*')      mem[i] = '?';
285                 else if (term[i] == '?') mem[i] = '#';
286                 else                     mem[i] = term[i];
287             }
288             mem[length] = '\0';
289             term = mem;
290         }
291         else {
292             /* No masking characters.  If there's no "truncation.none"
293              * configuration element, that's an error which we
294              * indicate (rather tangentially) as 30 "Too many masking
295              * characters in term".  28 would be equally meaningful
296              * (or meaningless) but using a different value allows us
297              * to differentiate between this case and the previous
298              * one.
299              */
300             cql_pr_attr(ct, "truncation.", "none", 0,
301                         pr, client_data, 30);
302         }
303     }
304
305     (*pr)("\"", client_data);
306     for (i = 0; i<length; i++)
307     {
308         char buf[2];
309         buf[0] = term[i];
310         buf[1] = 0;
311         (*pr)(buf, client_data);
312     }
313     (*pr)("\" ", client_data);
314 }
315
316 void emit_wordlist(cql_transform_t ct,
317                    struct cql_node *cn,
318                    void (*pr)(const char *buf, void *client_data),
319                    void *client_data,
320                    const char *op)
321 {
322     const char *cp0 = cn->u.st.term;
323     const char *cp1;
324     const char *last_term = 0;
325     int last_length = 0;
326     while(cp0)
327     {
328         while (*cp0 == ' ')
329             cp0++;
330         cp1 = strchr(cp0, ' ');
331         if (last_term)
332         {
333             (*pr)("@", client_data);
334             (*pr)(op, client_data);
335             (*pr)(" ", client_data);
336             emit_term(ct, last_term, last_length, pr, client_data);
337         }
338         last_term = cp0;
339         if (cp1)
340             last_length = cp1 - cp0;
341         else
342             last_length = strlen(cp0);
343         cp0 = cp1;
344     }
345     if (last_term)
346         emit_term(ct, last_term, last_length, pr, client_data);
347 }
348
349
350 static const char *cql_get_ns(cql_transform_t ct,
351                               struct cql_node *cn,
352                               struct cql_node **prefix_ar, int prefix_level,
353                               const char **n_prefix,
354                               const char **n_suffix)
355 {
356     int i;
357     const char *ns = 0;
358     char prefix[32];
359     const char *cp = cn->u.st.index;
360     const char *cp_dot = strchr(cp, '.');
361
362     /* strz current prefix (empty if not given) */
363     if (cp_dot && cp_dot-cp < sizeof(prefix))
364     {
365         memcpy (prefix, cp, cp_dot - cp);
366         prefix[cp_dot - cp] = 0;
367     }
368     else
369         *prefix = 0;
370
371     /* 2. lookup in prefix_ar. and return NS */
372     for (i = prefix_level; !ns && --i >= 0; )
373     {
374         struct cql_node *cn_prefix = prefix_ar[i];
375         for (; cn_prefix; cn_prefix = cn_prefix->u.st.modifiers)
376         {
377             if (*prefix && cn_prefix->u.st.index &&
378                 !strcmp(prefix, cn_prefix->u.st.index))
379             {
380                 ns = cn_prefix->u.st.term;
381                 break;
382             }
383             else if (!*prefix && !cn_prefix->u.st.index)
384             {
385                 ns = cn_prefix->u.st.term;
386                 break;
387             }
388         }
389     }
390     if (!ns)
391     {
392         if (!ct->error)
393         {
394             ct->error = 15;
395             ct->addinfo = strdup(prefix);
396         }
397         return 0;
398     }
399     /* 3. lookup in set.NS for new prefix */
400     *n_prefix = cql_lookup_value(ct, "set.", ns);
401     if (!*n_prefix)
402     {
403         if (!ct->error)
404         {
405             ct->error = 15;
406             ct->addinfo = strdup(ns);
407         }
408         return 0;
409     }
410     /* 4. lookup index.prefix. */
411     
412     cp = cn->u.st.index;
413     cp_dot = strchr(cp, '.');
414     
415     *n_suffix = cp_dot ? cp_dot+1 : cp;
416     return ns;
417 }
418
419 void cql_transform_r(cql_transform_t ct,
420                      struct cql_node *cn,
421                      void (*pr)(const char *buf, void *client_data),
422                      void *client_data,
423                      struct cql_node **prefix_ar, int prefix_level)
424 {
425     const char *ns, *n_prefix, *n_suffix;
426
427     if (!cn)
428         return;
429     switch (cn->which)
430     {
431     case CQL_NODE_ST:
432         if (cn->u.st.prefixes && prefix_level < 20)
433             prefix_ar[prefix_level++] = cn->u.st.prefixes;
434         ns = cql_get_ns(ct, cn, prefix_ar, prefix_level, &n_prefix, &n_suffix);
435         if (ns)
436         {
437             char n_full[64];
438             sprintf (n_full, "%.20s.%.40s", n_prefix, n_suffix);
439         
440             if ((!strcmp(ns, "http://www.loc.gov/zing/cql/context-sets/cql/v1.1/") ||
441                  !strcmp(ns, "http://www.loc.gov/zing/cql/srw-indexes/v1.0/"))
442                 && !strcmp(n_suffix, "resultSet"))
443             {
444                 (*pr)("@set \"", client_data);
445                 (*pr)(cn->u.st.term, client_data);
446                 (*pr)("\" ", client_data);
447                 return ;
448             }
449             /* ### It would be nice if this could fall back to whichever 
450                of cql.serverChoice and srw.serverChoice is defined */
451             if (!cql_pr_attr(ct, "index.", n_full, "cql.serverChoice",
452                              pr, client_data, 16)) {
453                 /* No index.foo; reset error and fall back to qualifier.foo */
454                 if (ct->error == 16) ct->error = 0;
455                 cql_pr_attr(ct, "qualifier.", n_full, "cql.serverChoice",
456                             pr, client_data, 16);
457             }
458         }
459
460         if (cn->u.st.relation && !strcmp(cn->u.st.relation, "="))
461             cql_pr_attr(ct, "relation.", "eq", "scr",
462                         pr, client_data, 19);
463         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, "<="))
464             cql_pr_attr(ct, "relation.", "le", "scr",
465                         pr, client_data, 19);
466         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, ">="))
467             cql_pr_attr(ct, "relation.", "ge", "scr",
468                         pr, client_data, 19);
469         else
470             cql_pr_attr(ct, "relation.", cn->u.st.relation, "eq",
471                         pr, client_data, 19);
472         if (cn->u.st.modifiers)
473         {
474             struct cql_node *mod = cn->u.st.modifiers;
475             for (; mod; mod = mod->u.st.modifiers)
476             {
477                 cql_pr_attr(ct, "relationModifier.", mod->u.st.term, 0,
478                             pr, client_data, 20);
479             }
480         }
481         cql_pr_attr(ct, "structure.", cn->u.st.relation, 0,
482                     pr, client_data, 24);
483         if (cn->u.st.relation && !strcmp(cn->u.st.relation, "all"))
484         {
485             emit_wordlist(ct, cn, pr, client_data, "and");
486         }
487         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, "any"))
488         {
489             emit_wordlist(ct, cn, pr, client_data, "or");
490         }
491         else
492         {
493             emit_term(ct, cn->u.st.term, strlen(cn->u.st.term),
494                       pr, client_data);
495         }
496         break;
497     case CQL_NODE_BOOL:
498         if (cn->u.boolean.prefixes && prefix_level < 20)
499             prefix_ar[prefix_level++] = cn->u.boolean.prefixes;
500         (*pr)("@", client_data);
501         (*pr)(cn->u.boolean.value, client_data);
502         (*pr)(" ", client_data);
503
504         cql_transform_r(ct, cn->u.boolean.left, pr, client_data,
505                         prefix_ar, prefix_level);
506         cql_transform_r(ct, cn->u.boolean.right, pr, client_data,
507                         prefix_ar, prefix_level);
508     }
509 }
510
511 int cql_transform(cql_transform_t ct,
512                   struct cql_node *cn,
513                   void (*pr)(const char *buf, void *client_data),
514                   void *client_data)
515 {
516     struct cql_node *prefix_ar[20], **pp;
517     struct cql_prop_entry *e;
518
519     ct->error = 0;
520     if (ct->addinfo)
521         free (ct->addinfo);
522     ct->addinfo = 0;
523
524     prefix_ar[0] = 0;
525     pp = &prefix_ar[0];
526     for (e = ct->entry; e ; e = e->next)
527     {
528         if (!memcmp(e->pattern, "set.", 4))
529         {
530             *pp = cql_node_mk_sc(e->pattern+4, "=", e->value);
531             pp = &(*pp)->u.st.modifiers;
532         }
533         else if (!strcmp(e->pattern, "set"))
534         {
535             *pp = cql_node_mk_sc(e->value, 0, 0);
536             pp = &(*pp)->u.st.modifiers;
537         }
538     }
539     cql_transform_r (ct, cn, pr, client_data, prefix_ar, 1);
540     cql_node_destroy(prefix_ar[0]);
541     return ct->error;
542 }
543
544
545 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
546 {
547     return cql_transform(ct, cn, cql_fputs, f);
548 }
549
550 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
551                       char *out, int max)
552 {
553     struct cql_buf_write_info info;
554     int r;
555
556     info.off = 0;
557     info.max = max;
558     info.buf = out;
559     r = cql_transform(ct, cn, cql_buf_write_handler, &info);
560     if (info.off >= 0)
561         info.buf[info.off] = '\0';
562     return r;
563 }
564
565 int cql_transform_error(cql_transform_t ct, const char **addinfo)
566 {
567     *addinfo = ct->addinfo;
568     return ct->error;
569 }