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