Fixed bug #678: CQL to PQF translation does not preserve double-quote
[yaz-moved-to-github.git] / src / cqltransform.c
1 /* $Id: cqltransform.c,v 1.24 2006-10-05 16:12:23 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         /* pr(int) each character */
448         char buf[3];
449         const char *cp;
450
451         buf[1] = term[i];
452         buf[2] = 0;
453         /* do we have to escape this char? */
454         if (buf[1] == '"')
455         {
456             buf[0] = '\\';
457             cp = buf;
458         }
459         else
460             cp = buf+1;
461         (*pr)(cp, client_data);
462     }
463     (*pr)("\" ", client_data);
464 }
465
466 void emit_wordlist(cql_transform_t ct,
467                    struct cql_node *cn,
468                    void (*pr)(const char *buf, void *client_data),
469                    void *client_data,
470                    const char *op)
471 {
472     const char *cp0 = cn->u.st.term;
473     const char *cp1;
474     const char *last_term = 0;
475     int last_length = 0;
476     while(cp0)
477     {
478         while (*cp0 == ' ')
479             cp0++;
480         cp1 = strchr(cp0, ' ');
481         if (last_term)
482         {
483             (*pr)("@", client_data);
484             (*pr)(op, client_data);
485             (*pr)(" ", client_data);
486             emit_term(ct, cn, last_term, last_length, pr, client_data);
487         }
488         last_term = cp0;
489         if (cp1)
490             last_length = cp1 - cp0;
491         else
492             last_length = strlen(cp0);
493         cp0 = cp1;
494     }
495     if (last_term)
496         emit_term(ct, cn, last_term, last_length, pr, client_data);
497 }
498
499 void cql_transform_r(cql_transform_t ct,
500                      struct cql_node *cn,
501                      void (*pr)(const char *buf, void *client_data),
502                      void *client_data)
503 {
504     const char *ns;
505     struct cql_node *mods;
506
507     if (!cn)
508         return;
509     switch (cn->which)
510     {
511     case CQL_NODE_ST:
512         ns = cn->u.st.index_uri;
513         if (ns)
514         {
515             if (!strcmp(ns, cql_uri())
516                 && cn->u.st.index && !cql_strcmp(cn->u.st.index, "resultSet"))
517             {
518                 (*pr)("@set \"", client_data);
519                 (*pr)(cn->u.st.term, client_data);
520                 (*pr)("\" ", client_data);
521                 return ;
522             }
523         }
524         else
525         {
526             if (!ct->error)
527             {
528                 ct->error = 15;
529                 ct->addinfo = 0;
530             }
531         }
532         cql_pr_attr(ct, "always", 0, 0, pr, client_data, 0);
533         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "="))
534             cql_pr_attr(ct, "relation", "eq", "scr",
535                         pr, client_data, 19);
536         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "<="))
537             cql_pr_attr(ct, "relation", "le", "scr",
538                         pr, client_data, 19);
539         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, ">="))
540             cql_pr_attr(ct, "relation", "ge", "scr",
541                         pr, client_data, 19);
542         else
543             cql_pr_attr(ct, "relation", cn->u.st.relation, "eq",
544                         pr, client_data, 19);
545         cql_pr_attr(ct, "structure", cn->u.st.relation, 0,
546                     pr, client_data, 24);
547         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "all"))
548         {
549             emit_wordlist(ct, cn, pr, client_data, "and");
550         }
551         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "any"))
552         {
553             emit_wordlist(ct, cn, pr, client_data, "or");
554         }
555         else
556         {
557             emit_term(ct, cn, cn->u.st.term, strlen(cn->u.st.term),
558                       pr, client_data);
559         }
560         break;
561     case CQL_NODE_BOOL:
562         (*pr)("@", client_data);
563         (*pr)(cn->u.boolean.value, client_data);
564         (*pr)(" ", client_data);
565         mods = cn->u.boolean.modifiers;
566         if (!strcmp(cn->u.boolean.value, "prox")) {
567             if (!cql_pr_prox(ct, mods, pr, client_data))
568                 return;
569         } else if (mods) {
570             /* Boolean modifiers other than on proximity not supported */
571             ct->error = 46; /* SRW diag: "Unsupported boolean modifier" */
572             ct->addinfo = xstrdup(mods->u.st.index);
573             return;
574         }
575
576         cql_transform_r(ct, cn->u.boolean.left, pr, client_data);
577         cql_transform_r(ct, cn->u.boolean.right, pr, client_data);
578         break;
579
580     default:
581         fprintf(stderr, "Fatal: impossible CQL node-type %d\n", cn->which);
582         abort();
583     }
584 }
585
586 int cql_transform(cql_transform_t ct,
587                   struct cql_node *cn,
588                   void (*pr)(const char *buf, void *client_data),
589                   void *client_data)
590 {
591     struct cql_prop_entry *e;
592     NMEM nmem = nmem_create();
593
594     ct->error = 0;
595     if (ct->addinfo)
596         xfree (ct->addinfo);
597     ct->addinfo = 0;
598
599     for (e = ct->entry; e ; e = e->next)
600     {
601         if (!cql_strncmp(e->pattern, "set.", 4))
602             cql_apply_prefix(nmem, cn, e->pattern+4, e->value);
603         else if (!cql_strcmp(e->pattern, "set"))
604             cql_apply_prefix(nmem, cn, 0, e->value);
605     }
606     cql_transform_r (ct, cn, pr, client_data);
607     nmem_destroy(nmem);
608     return ct->error;
609 }
610
611
612 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
613 {
614     return cql_transform(ct, cn, cql_fputs, f);
615 }
616
617 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
618                       char *out, int max)
619 {
620     struct cql_buf_write_info info;
621     int r;
622
623     info.off = 0;
624     info.max = max;
625     info.buf = out;
626     r = cql_transform(ct, cn, cql_buf_write_handler, &info);
627     if (info.off < 0) {
628         /* Attempt to write past end of buffer.  For some reason, this
629            SRW diagnostic is deprecated, but it's so perfect for our
630            purposes that it would be stupid not to use it. */
631         char numbuf[30];
632         ct->error = YAZ_SRW_TOO_MANY_CHARS_IN_QUERY;
633         sprintf(numbuf, "%ld", (long) info.max);
634         ct->addinfo = xstrdup(numbuf);
635         return -1;
636     }
637     if (info.off >= 0)
638         info.buf[info.off] = '\0';
639     return r;
640 }
641
642 int cql_transform_error(cql_transform_t ct, const char **addinfo)
643 {
644     *addinfo = ct->addinfo;
645     return ct->error;
646 }
647 /*
648  * Local variables:
649  * c-basic-offset: 4
650  * indent-tabs-mode: nil
651  * End:
652  * vim: shiftwidth=4 tabstop=8 expandtab
653  */
654