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