Fixed bug in yaz_srw_get: case Z_SRW_scan_response missed a break.
[yaz-moved-to-github.git] / src / cqltransform.c
1 /* $Id: cqltransform.c,v 1.22 2006-04-05 12:04:51 mike Exp $
2    Copyright (C) 1995-2005, Index Data ApS
3    Index Data Aps
4
5 This file is part of the YAZ toolkit.
6
7 See the file LICENSE.
8 */
9
10 /**
11  * \file cqltransform.c
12  * \brief Implements CQL transform (CQL to RPN conversion).
13  */
14
15 #include <stdlib.h>
16 #include <string.h>
17 #include <yaz/cql.h>
18 #include <yaz/xmalloc.h>
19 #include <yaz/diagsrw.h>
20
21 struct cql_prop_entry {
22     char *pattern;
23     char *value;
24     struct cql_prop_entry *next;
25 };
26
27 struct cql_transform_t_ {
28     struct cql_prop_entry *entry;
29     int error;
30     char *addinfo;
31 };
32
33 cql_transform_t cql_transform_open_FILE(FILE *f)
34 {
35     char line[1024];
36     cql_transform_t ct = (cql_transform_t) xmalloc (sizeof(*ct));
37     struct cql_prop_entry **pp = &ct->entry;
38
39     ct->error = 0;
40     ct->addinfo = 0;
41     while (fgets(line, sizeof(line)-1, f))
42     {
43         const char *cp_value_start;
44         const char *cp_value_end;
45         const char *cp_pattern_end;
46         const char *cp = line;
47         while (*cp && !strchr(" \t=\r\n#", *cp))
48             cp++;
49         cp_pattern_end = cp;
50         if (cp == line)
51             continue;
52         while (*cp && strchr(" \t\r\n", *cp))
53             cp++;
54         if (*cp != '=')
55             continue;
56         cp++;
57         while (*cp && strchr(" \t\r\n", *cp))
58             cp++;
59         cp_value_start = cp;
60         if (!(cp_value_end = strchr(cp, '#')))
61             cp_value_end = strlen(line) + line;
62
63         if (cp_value_end != cp_value_start &&
64             strchr(" \t\r\n", cp_value_end[-1]))
65             cp_value_end--;
66         *pp = (struct cql_prop_entry *) xmalloc (sizeof(**pp));
67         (*pp)->pattern = (char *) xmalloc (cp_pattern_end - line + 1);
68         memcpy ((*pp)->pattern, line, cp_pattern_end - line);
69         (*pp)->pattern[cp_pattern_end-line] = 0;
70
71         (*pp)->value = (char *) xmalloc (cp_value_end - cp_value_start + 1);
72         if (cp_value_start != cp_value_end)
73             memcpy ((*pp)->value, cp_value_start, cp_value_end-cp_value_start);
74         (*pp)->value[cp_value_end - cp_value_start] = 0;
75         pp = &(*pp)->next;
76     }
77     *pp = 0;
78     return ct;
79 }
80
81 void cql_transform_close(cql_transform_t ct)
82 {
83     struct cql_prop_entry *pe;
84     if (!ct)
85         return;
86     pe = ct->entry;
87     while (pe)
88     {
89         struct cql_prop_entry *pe_next = pe->next;
90         xfree (pe->pattern);
91         xfree (pe->value);
92         xfree (pe);
93         pe = pe_next;
94     }
95     if (ct->addinfo)
96         xfree (ct->addinfo);
97     xfree (ct);
98 }
99
100 cql_transform_t cql_transform_open_fname(const char *fname)
101 {
102     cql_transform_t ct;
103     FILE *f = fopen(fname, "r");
104     if (!f)
105         return 0;
106     ct = cql_transform_open_FILE(f);
107     fclose(f);
108     return ct;
109 }
110
111 static const char *cql_lookup_property(cql_transform_t ct,
112                                        const char *pat1, const char *pat2,
113                                        const char *pat3)
114 {
115     char pattern[120];
116     struct cql_prop_entry *e;
117
118     if (pat1 && pat2 && pat3)
119         sprintf (pattern, "%.39s.%.39s.%.39s", pat1, pat2, pat3);
120     else if (pat1 && pat2)
121         sprintf (pattern, "%.39s.%.39s", pat1, pat2);
122     else if (pat1 && pat3)
123         sprintf (pattern, "%.39s.%.39s", pat1, pat3);
124     else if (pat1)
125         sprintf (pattern, "%.39s", pat1);
126     else
127         return 0;
128     
129     for (e = ct->entry; e; e = e->next)
130     {
131         if (!cql_strcmp(e->pattern, pattern))
132             return e->value;
133     }
134     return 0;
135 }
136
137 int cql_pr_attr_uri(cql_transform_t ct, const char *category,
138                    const char *uri, const char *val, const char *default_val,
139                    void (*pr)(const char *buf, void *client_data),
140                    void *client_data,
141                    int errcode)
142 {
143     const char *res = 0;
144     const char *eval = val ? val : default_val;
145     const char *prefix = 0;
146     
147     if (uri)
148     {
149         struct cql_prop_entry *e;
150         
151         for (e = ct->entry; e; e = e->next)
152             if (!memcmp(e->pattern, "set.", 4) && e->value &&
153                 !strcmp(e->value, uri))
154             {
155                 prefix = e->pattern+4;
156                 break;
157             }
158         /* must have a prefix now - if not it's an error */
159     }
160
161     if (!uri || prefix)
162     {
163         if (!res)
164             res = cql_lookup_property(ct, category, prefix, eval);
165         if (!res)
166             res = cql_lookup_property(ct, category, prefix, "*");
167     }
168     if (res)
169     {
170         char buf[64];
171
172         const char *cp0 = res, *cp1;
173         while ((cp1 = strchr(cp0, '=')))
174         {
175             while (*cp1 && *cp1 != ' ')
176                 cp1++;
177             if (cp1 - cp0 >= sizeof(buf))
178                 break;
179             memcpy (buf, cp0, cp1 - cp0);
180             buf[cp1-cp0] = 0;
181             (*pr)("@attr ", client_data);
182             (*pr)(buf, client_data);
183             (*pr)(" ", client_data);
184             cp0 = cp1;
185             while (*cp0 == ' ')
186                 cp0++;
187         }
188         return 1;
189     }
190     /* error ... */
191     if (errcode && !ct->error)
192     {
193         ct->error = errcode;
194         if (val)
195             ct->addinfo = xstrdup(val);
196         else
197             ct->addinfo = 0;
198     }
199     return 0;
200 }
201
202 int cql_pr_attr(cql_transform_t ct, const char *category,
203                 const char *val, const char *default_val,
204                 void (*pr)(const char *buf, void *client_data),
205                 void *client_data,
206                 int errcode)
207 {
208     return cql_pr_attr_uri(ct, category, 0 /* uri */,
209                            val, default_val, pr, client_data, errcode);
210 }
211
212
213 static void cql_pr_int (int val,
214                         void (*pr)(const char *buf, void *client_data),
215                         void *client_data)
216 {
217     char buf[21];              /* enough characters to 2^64 */
218     sprintf(buf, "%d", val);
219     (*pr)(buf, client_data);
220     (*pr)(" ", client_data);
221 }
222
223
224 static int cql_pr_prox(cql_transform_t ct, struct cql_node *mods,
225                        void (*pr)(const char *buf, void *client_data),
226                        void *client_data)
227 {
228     int exclusion = 0;
229     int distance;               /* to be filled in later depending on unit */
230     int distance_defined = 0;
231     int ordered = 0;
232     int proxrel = 2;            /* less than or equal */
233     int unit = 2;               /* word */
234
235     while (mods != 0) {
236         char *name = mods->u.st.index;
237         char *term = mods->u.st.term;
238         char *relation = mods->u.st.relation;
239
240         if (!strcmp(name, "distance")) {
241             distance = strtol(term, (char**) 0, 0);
242             distance_defined = 1;
243             if (!strcmp(relation, "=")) {
244                 proxrel = 3;
245             } else if (!strcmp(relation, ">")) {
246                 proxrel = 5;
247             } else if (!strcmp(relation, "<")) {
248                 proxrel = 1;
249             } else if (!strcmp(relation, ">=")) {
250                 proxrel = 4;
251             } else if (!strcmp(relation, "<=")) {
252                 proxrel = 2;
253             } else if (!strcmp(relation, "<>")) {
254                 proxrel = 6;
255             } else {
256                 ct->error = 40; /* Unsupported proximity relation */
257                 ct->addinfo = xstrdup(relation);
258                 return 0;
259             }
260         } else if (!strcmp(name, "ordered")) {
261             ordered = 1;
262         } else if (!strcmp(name, "unordered")) {
263             ordered = 0;
264         } else if (!strcmp(name, "unit")) {
265             if (!strcmp(term, "word")) {
266                 unit = 2;
267             } else if (!strcmp(term, "sentence")) {
268                 unit = 3;
269             } else if (!strcmp(term, "paragraph")) {
270                 unit = 4;
271             } else if (!strcmp(term, "element")) {
272                 unit = 8;
273             } else {
274                 ct->error = 42; /* Unsupported proximity unit */
275                 ct->addinfo = xstrdup(term);
276                 return 0;
277             }
278         } else {
279             ct->error = 46;     /* Unsupported boolean modifier */
280             ct->addinfo = xstrdup(name);
281             return 0;
282         }
283
284         mods = mods->u.st.modifiers;
285     }
286
287     if (!distance_defined)
288         distance = (unit == 2) ? 1 : 0;
289
290     cql_pr_int(exclusion, pr, client_data);
291     cql_pr_int(distance, pr, client_data);
292     cql_pr_int(ordered, pr, client_data);
293     cql_pr_int(proxrel, pr, client_data);
294     (*pr)("k ", client_data);
295     cql_pr_int(unit, pr, client_data);
296
297     return 1;
298 }
299
300 /* Returns location of first wildcard character in the `length'
301  * characters starting at `term', or a null pointer of there are
302  * none -- like memchr().
303  */
304 static const char *wcchar(const char *term, int length)
305 {
306     const char *best = 0;
307     const char *current;
308     char *whichp;
309
310     for (whichp = "*?"; *whichp != '\0'; whichp++) {
311         current = (const char *) memchr(term, *whichp, length);
312         if (current != 0 && (best == 0 || current < best))
313             best = current;
314     }
315
316     return best;
317 }
318
319
320 void emit_term(cql_transform_t ct,
321                const char *term, int length,
322                void (*pr)(const char *buf, void *client_data),
323                void *client_data)
324 {
325     int i;
326     if (length > 0)
327     {
328         if (length > 1 && term[0] == '^' && term[length-1] == '^')
329         {
330             cql_pr_attr(ct, "position", "firstAndLast", 0,
331                         pr, client_data, 32);
332             term++;
333             length -= 2;
334         }
335         else if (term[0] == '^')
336         {
337             cql_pr_attr(ct, "position", "first", 0,
338                         pr, client_data, 32);
339             term++;
340             length--;
341         }
342         else if (term[length-1] == '^')
343         {
344             cql_pr_attr(ct, "position", "last", 0,
345                         pr, client_data, 32);
346             length--;
347         }
348         else
349         {
350             cql_pr_attr(ct, "position", "any", 0,
351                         pr, client_data, 32);
352         }
353     }
354
355     if (length > 0)
356     {
357         /* Check for well-known globbing patterns that represent
358          * simple truncation attributes as expected by, for example,
359          * Bath-compliant server.  If we find such a pattern but
360          * there's no mapping for it, that's fine: we just use a
361          * general pattern-matching attribute.
362          */
363         if (length > 1 && term[0] == '*' && term[length-1] == '*' &&
364             wcchar(term+1, length-2) == 0 &&
365             cql_pr_attr(ct, "truncation", "both", 0,
366                         pr, client_data, 0)) {
367             term++;
368             length -= 2;
369         }
370         else if (term[0] == '*' &&
371                  wcchar(term+1, length-1) == 0 &&
372                  cql_pr_attr(ct, "truncation", "left", 0,
373                              pr, client_data, 0)) {
374             term++;
375             length--;
376         }
377         else if (term[length-1] == '*' &&
378                  wcchar(term, length-1) == 0 &&
379                  cql_pr_attr(ct, "truncation", "right", 0,
380                              pr, client_data, 0)) {
381             length--;
382         }
383         else if (wcchar(term, length))
384         {
385             /* We have one or more wildcard characters, but not in a
386              * way that can be dealt with using only the standard
387              * left-, right- and both-truncation attributes.  We need
388              * to translate the pattern into a Z39.58-type pattern,
389              * which has been supported in BIB-1 since 1996.  If
390              * there's no configuration element for "truncation.z3958"
391              * we indicate this as error 28 "Masking character not
392              * supported".
393              */
394             int i;
395             char *mem;
396             cql_pr_attr(ct, "truncation", "z3958", 0,
397                         pr, client_data, 28);
398             mem = (char *) xmalloc(length+1);
399             for (i = 0; i < length; i++) {
400                 if (term[i] == '*')      mem[i] = '?';
401                 else if (term[i] == '?') mem[i] = '#';
402                 else                     mem[i] = term[i];
403             }
404             mem[length] = '\0';
405             term = mem;
406         }
407         else {
408             /* No masking characters.  Use "truncation.none" if given. */
409             cql_pr_attr(ct, "truncation", "none", 0,
410                         pr, client_data, 0);
411         }
412     }
413
414     (*pr)("\"", client_data);
415     for (i = 0; i<length; i++)
416     {
417         char buf[2];
418         buf[0] = term[i];
419         buf[1] = 0;
420         (*pr)(buf, client_data);
421     }
422     (*pr)("\" ", client_data);
423 }
424
425 void emit_wordlist(cql_transform_t ct,
426                    struct cql_node *cn,
427                    void (*pr)(const char *buf, void *client_data),
428                    void *client_data,
429                    const char *op)
430 {
431     const char *cp0 = cn->u.st.term;
432     const char *cp1;
433     const char *last_term = 0;
434     int last_length = 0;
435     while(cp0)
436     {
437         while (*cp0 == ' ')
438             cp0++;
439         cp1 = strchr(cp0, ' ');
440         if (last_term)
441         {
442             (*pr)("@", client_data);
443             (*pr)(op, client_data);
444             (*pr)(" ", client_data);
445             emit_term(ct, last_term, last_length, pr, client_data);
446         }
447         last_term = cp0;
448         if (cp1)
449             last_length = cp1 - cp0;
450         else
451             last_length = strlen(cp0);
452         cp0 = cp1;
453     }
454     if (last_term)
455         emit_term(ct, last_term, last_length, pr, client_data);
456 }
457
458 void cql_transform_r(cql_transform_t ct,
459                      struct cql_node *cn,
460                      void (*pr)(const char *buf, void *client_data),
461                      void *client_data)
462 {
463     const char *ns;
464     struct cql_node *mods;
465
466     if (!cn)
467         return;
468     switch (cn->which)
469     {
470     case CQL_NODE_ST:
471         ns = cn->u.st.index_uri;
472         if (ns)
473         {
474             if (!strcmp(ns, cql_uri())
475                 && cn->u.st.index && !cql_strcmp(cn->u.st.index, "resultSet"))
476             {
477                 (*pr)("@set \"", client_data);
478                 (*pr)(cn->u.st.term, client_data);
479                 (*pr)("\" ", client_data);
480                 return ;
481             }
482         }
483         else
484         {
485             if (!ct->error)
486             {
487                 ct->error = 15;
488                 ct->addinfo = 0;
489             }
490         }
491         cql_pr_attr(ct, "always", 0, 0, pr, client_data, 0);
492         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "="))
493             cql_pr_attr(ct, "relation", "eq", "scr",
494                         pr, client_data, 19);
495         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "<="))
496             cql_pr_attr(ct, "relation", "le", "scr",
497                         pr, client_data, 19);
498         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, ">="))
499             cql_pr_attr(ct, "relation", "ge", "scr",
500                         pr, client_data, 19);
501         else
502             cql_pr_attr(ct, "relation", cn->u.st.relation, "eq",
503                         pr, client_data, 19);
504         if (cn->u.st.modifiers)
505         {
506             struct cql_node *mod = cn->u.st.modifiers;
507             for (; mod; mod = mod->u.st.modifiers)
508             {
509                 cql_pr_attr(ct, "relationModifier", mod->u.st.index, 0,
510                             pr, client_data, 20);
511             }
512         }
513         cql_pr_attr(ct, "structure", cn->u.st.relation, 0,
514                     pr, client_data, 24);
515         if (ns) {
516             cql_pr_attr_uri(ct, "index", ns,
517                             cn->u.st.index, "serverChoice",
518                             pr, client_data, 16);
519         }
520         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "all"))
521         {
522             emit_wordlist(ct, cn, pr, client_data, "and");
523         }
524         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "any"))
525         {
526             emit_wordlist(ct, cn, pr, client_data, "or");
527         }
528         else
529         {
530             emit_term(ct, cn->u.st.term, strlen(cn->u.st.term),
531                       pr, client_data);
532         }
533         break;
534     case CQL_NODE_BOOL:
535         (*pr)("@", client_data);
536         (*pr)(cn->u.boolean.value, client_data);
537         (*pr)(" ", client_data);
538         mods = cn->u.boolean.modifiers;
539         if (!strcmp(cn->u.boolean.value, "prox")) {
540             if (!cql_pr_prox(ct, mods, pr, client_data))
541                 return;
542         } else if (mods) {
543             /* Boolean modifiers other than on proximity not supported */
544             ct->error = 46; /* SRW diag: "Unsupported boolean modifier" */
545             ct->addinfo = xstrdup(mods->u.st.index);
546             return;
547         }
548
549         cql_transform_r(ct, cn->u.boolean.left, pr, client_data);
550         cql_transform_r(ct, cn->u.boolean.right, pr, client_data);
551         break;
552
553     default:
554         fprintf(stderr, "Fatal: impossible CQL node-type %d\n", cn->which);
555         abort();
556     }
557 }
558
559 int cql_transform(cql_transform_t ct,
560                   struct cql_node *cn,
561                   void (*pr)(const char *buf, void *client_data),
562                   void *client_data)
563 {
564     struct cql_prop_entry *e;
565     NMEM nmem = nmem_create();
566
567     ct->error = 0;
568     if (ct->addinfo)
569         xfree (ct->addinfo);
570     ct->addinfo = 0;
571
572     for (e = ct->entry; e ; e = e->next)
573     {
574         if (!cql_strncmp(e->pattern, "set.", 4))
575             cql_apply_prefix(nmem, cn, e->pattern+4, e->value);
576         else if (!cql_strcmp(e->pattern, "set"))
577             cql_apply_prefix(nmem, cn, 0, e->value);
578     }
579     cql_transform_r (ct, cn, pr, client_data);
580     nmem_destroy(nmem);
581     return ct->error;
582 }
583
584
585 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
586 {
587     return cql_transform(ct, cn, cql_fputs, f);
588 }
589
590 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
591                       char *out, int max)
592 {
593     struct cql_buf_write_info info;
594     int r;
595
596     info.off = 0;
597     info.max = max;
598     info.buf = out;
599     r = cql_transform(ct, cn, cql_buf_write_handler, &info);
600     if (info.off < 0) {
601         /* Attempt to write past end of buffer.  For some reason, this
602            SRW diagnostic is deprecated, but it's so perfect for our
603            purposes that it would be stupid not to use it. */
604         char numbuf[30];
605         ct->error = YAZ_SRW_TOO_MANY_CHARS_IN_QUERY;
606         sprintf(numbuf, "%ld", (long) info.max);
607         ct->addinfo = xstrdup(numbuf);
608         return -1;
609     }
610     if (info.off >= 0)
611         info.buf[info.off] = '\0';
612     return r;
613 }
614
615 int cql_transform_error(cql_transform_t ct, const char **addinfo)
616 {
617     *addinfo = ct->addinfo;
618     return ct->error;
619 }
620 /*
621  * Local variables:
622  * c-basic-offset: 4
623  * indent-tabs-mode: nil
624  * End:
625  * vim: shiftwidth=4 tabstop=8 expandtab
626  */
627