Fixed bug #1988: CQL-to-PQF translation mishandles quoted doube.
[yaz-moved-to-github.git] / src / cqltransform.c
1 /* $Id: cqltransform.c,v 1.30 2007-12-20 22:45:37 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(int start, const char *term, int length)
341 {
342     while (length > 0)
343     {
344         if (start || term[-1] != '\\')
345             if (strchr("*?", *term))
346                 return term;
347         term++;
348         length--;
349         start = 0;
350     }
351     return 0;
352 }
353
354
355 /* ### checks for CQL relation-name rather than Type-1 attribute */
356 static int has_modifier(struct cql_node *cn, const char *name) {
357     struct cql_node *mod;
358     for (mod = cn->u.st.modifiers; mod != 0; mod = mod->u.st.modifiers) {
359         if (!strcmp(mod->u.st.index, name))
360             return 1;
361     }
362
363     return 0;
364 }
365
366
367 void emit_term(cql_transform_t ct,
368                struct cql_node *cn,
369                const char *term, int length,
370                void (*pr)(const char *buf, void *client_data),
371                void *client_data)
372 {
373     int i;
374     const char *ns = cn->u.st.index_uri;
375     int process_term = !has_modifier(cn, "regexp");
376     char *z3958_mem = 0;
377
378     assert(cn->which == CQL_NODE_ST);
379
380     if (process_term && length > 0)
381     {
382         if (length > 1 && term[0] == '^' && term[length-1] == '^')
383         {
384             cql_pr_attr(ct, "position", "firstAndLast", 0,
385                         pr, client_data, 32);
386             term++;
387             length -= 2;
388         }
389         else if (term[0] == '^')
390         {
391             cql_pr_attr(ct, "position", "first", 0,
392                         pr, client_data, 32);
393             term++;
394             length--;
395         }
396         else if (term[length-1] == '^')
397         {
398             cql_pr_attr(ct, "position", "last", 0,
399                         pr, client_data, 32);
400             length--;
401         }
402         else
403         {
404             cql_pr_attr(ct, "position", "any", 0,
405                         pr, client_data, 32);
406         }
407     }
408
409     if (process_term && length > 0)
410     {
411         const char *first_wc = wcchar(1, term, length);
412         const char *second_wc = first_wc ?
413             wcchar(0, first_wc+1, length-(first_wc-term)-1) : 0;
414
415         /* Check for well-known globbing patterns that represent
416          * simple truncation attributes as expected by, for example,
417          * Bath-compliant server.  If we find such a pattern but
418          * there's no mapping for it, that's fine: we just use a
419          * general pattern-matching attribute.
420          */
421         if (first_wc == term && second_wc == term + length-1 
422             && *first_wc == '*' && *second_wc == '*' 
423             && cql_pr_attr(ct, "truncation", "both", 0, pr, client_data, 0)) 
424         {
425             term++;
426             length -= 2;
427         }
428         else if (first_wc == term && second_wc == 0 && *first_wc == '*'
429                  && cql_pr_attr(ct, "truncation", "left", 0,
430                                 pr, client_data, 0))
431         {
432             term++;
433             length--;
434         }
435         else if (first_wc == term + length-1 && second_wc == 0
436                  && *first_wc == '*'
437                  && cql_pr_attr(ct, "truncation", "right", 0, 
438                                 pr, client_data, 0))
439         {
440             length--;
441         }
442         else if (first_wc)
443         {
444             /* We have one or more wildcard characters, but not in a
445              * way that can be dealt with using only the standard
446              * left-, right- and both-truncation attributes.  We need
447              * to translate the pattern into a Z39.58-type pattern,
448              * which has been supported in BIB-1 since 1996.  If
449              * there's no configuration element for "truncation.z3958"
450              * we indicate this as error 28 "Masking character not
451              * supported".
452              */
453             int i;
454             cql_pr_attr(ct, "truncation", "z3958", 0,
455                         pr, client_data, 28);
456             z3958_mem = (char *) xmalloc(length+1);
457             for (i = 0; i < length; i++)
458             {
459                 if (i > 0 && term[i-1] == '\\')
460                     z3958_mem[i] = term[i];
461                 else if (term[i] == '*')
462                     z3958_mem[i] = '?';
463                 else if (term[i] == '?')
464                     z3958_mem[i] = '#';
465                 else
466                     z3958_mem[i] = term[i];
467             }
468             z3958_mem[length] = '\0';
469             term = z3958_mem;
470         }
471         else {
472             /* No masking characters.  Use "truncation.none" if given. */
473             cql_pr_attr(ct, "truncation", "none", 0,
474                         pr, client_data, 0);
475         }
476     }
477     if (ns) {
478         cql_pr_attr_uri(ct, "index", ns,
479                         cn->u.st.index, "serverChoice",
480                         pr, client_data, 16);
481     }
482     if (cn->u.st.modifiers)
483     {
484         struct cql_node *mod = cn->u.st.modifiers;
485         for (; mod; mod = mod->u.st.modifiers)
486         {
487             cql_pr_attr(ct, "relationModifier", mod->u.st.index, 0,
488                         pr, client_data, 20);
489         }
490     }
491
492     (*pr)("\"", client_data);
493     for (i = 0; i<length; i++)
494     {
495         /* pr(int) each character */
496         /* we do not need to deal with \-sequences because the
497            CQL and PQF terms have same \-format, bug #1988 */
498         char buf[2];
499
500         buf[0] = term[i];
501         buf[1] = '\0';
502         (*pr)(buf, client_data);
503     }
504     (*pr)("\" ", client_data);
505     xfree(z3958_mem);
506 }
507
508 void emit_wordlist(cql_transform_t ct,
509                    struct cql_node *cn,
510                    void (*pr)(const char *buf, void *client_data),
511                    void *client_data,
512                    const char *op)
513 {
514     const char *cp0 = cn->u.st.term;
515     const char *cp1;
516     const char *last_term = 0;
517     int last_length = 0;
518     while(cp0)
519     {
520         while (*cp0 == ' ')
521             cp0++;
522         cp1 = strchr(cp0, ' ');
523         if (last_term)
524         {
525             (*pr)("@", client_data);
526             (*pr)(op, client_data);
527             (*pr)(" ", client_data);
528             emit_term(ct, cn, last_term, last_length, pr, client_data);
529         }
530         last_term = cp0;
531         if (cp1)
532             last_length = cp1 - cp0;
533         else
534             last_length = strlen(cp0);
535         cp0 = cp1;
536     }
537     if (last_term)
538         emit_term(ct, cn, last_term, last_length, pr, client_data);
539 }
540
541 void cql_transform_r(cql_transform_t ct,
542                      struct cql_node *cn,
543                      void (*pr)(const char *buf, void *client_data),
544                      void *client_data)
545 {
546     const char *ns;
547     struct cql_node *mods;
548
549     if (!cn)
550         return;
551     switch (cn->which)
552     {
553     case CQL_NODE_ST:
554         ns = cn->u.st.index_uri;
555         if (ns)
556         {
557             if (!strcmp(ns, cql_uri())
558                 && cn->u.st.index && !cql_strcmp(cn->u.st.index, "resultSet"))
559             {
560                 (*pr)("@set \"", client_data);
561                 (*pr)(cn->u.st.term, client_data);
562                 (*pr)("\" ", client_data);
563                 return ;
564             }
565         }
566         else
567         {
568             if (!ct->error)
569             {
570                 ct->error = 15;
571                 ct->addinfo = 0;
572             }
573         }
574         cql_pr_attr(ct, "always", 0, 0, pr, client_data, 0);
575         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "="))
576             cql_pr_attr(ct, "relation", "eq", "scr",
577                         pr, client_data, 19);
578         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "<="))
579             cql_pr_attr(ct, "relation", "le", "scr",
580                         pr, client_data, 19);
581         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, ">="))
582             cql_pr_attr(ct, "relation", "ge", "scr",
583                         pr, client_data, 19);
584         else
585             cql_pr_attr(ct, "relation", cn->u.st.relation, "eq",
586                         pr, client_data, 19);
587         cql_pr_attr(ct, "structure", cn->u.st.relation, 0,
588                     pr, client_data, 24);
589         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "all"))
590         {
591             emit_wordlist(ct, cn, pr, client_data, "and");
592         }
593         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "any"))
594         {
595             emit_wordlist(ct, cn, pr, client_data, "or");
596         }
597         else
598         {
599             emit_term(ct, cn, cn->u.st.term, strlen(cn->u.st.term),
600                       pr, client_data);
601         }
602         break;
603     case CQL_NODE_BOOL:
604         (*pr)("@", client_data);
605         (*pr)(cn->u.boolean.value, client_data);
606         (*pr)(" ", client_data);
607         mods = cn->u.boolean.modifiers;
608         if (!strcmp(cn->u.boolean.value, "prox")) {
609             if (!cql_pr_prox(ct, mods, pr, client_data))
610                 return;
611         } else if (mods) {
612             /* Boolean modifiers other than on proximity not supported */
613             ct->error = 46; /* SRW diag: "Unsupported boolean modifier" */
614             ct->addinfo = xstrdup(mods->u.st.index);
615             return;
616         }
617
618         cql_transform_r(ct, cn->u.boolean.left, pr, client_data);
619         cql_transform_r(ct, cn->u.boolean.right, pr, client_data);
620         break;
621
622     default:
623         fprintf(stderr, "Fatal: impossible CQL node-type %d\n", cn->which);
624         abort();
625     }
626 }
627
628 int cql_transform(cql_transform_t ct,
629                   struct cql_node *cn,
630                   void (*pr)(const char *buf, void *client_data),
631                   void *client_data)
632 {
633     struct cql_prop_entry *e;
634     NMEM nmem = nmem_create();
635
636     ct->error = 0;
637     if (ct->addinfo)
638         xfree (ct->addinfo);
639     ct->addinfo = 0;
640
641     for (e = ct->entry; e ; e = e->next)
642     {
643         if (!cql_strncmp(e->pattern, "set.", 4))
644             cql_apply_prefix(nmem, cn, e->pattern+4, e->value);
645         else if (!cql_strcmp(e->pattern, "set"))
646             cql_apply_prefix(nmem, cn, 0, e->value);
647     }
648     cql_transform_r (ct, cn, pr, client_data);
649     nmem_destroy(nmem);
650     return ct->error;
651 }
652
653
654 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
655 {
656     return cql_transform(ct, cn, cql_fputs, f);
657 }
658
659 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
660                       char *out, int max)
661 {
662     struct cql_buf_write_info info;
663     int r;
664
665     info.off = 0;
666     info.max = max;
667     info.buf = out;
668     r = cql_transform(ct, cn, cql_buf_write_handler, &info);
669     if (info.off < 0) {
670         /* Attempt to write past end of buffer.  For some reason, this
671            SRW diagnostic is deprecated, but it's so perfect for our
672            purposes that it would be stupid not to use it. */
673         char numbuf[30];
674         ct->error = YAZ_SRW_TOO_MANY_CHARS_IN_QUERY;
675         sprintf(numbuf, "%ld", (long) info.max);
676         ct->addinfo = xstrdup(numbuf);
677         return -1;
678     }
679     if (info.off >= 0)
680         info.buf[info.off] = '\0';
681     return r;
682 }
683
684 int cql_transform_error(cql_transform_t ct, const char **addinfo)
685 {
686     *addinfo = ct->addinfo;
687     return ct->error;
688 }
689 /*
690  * Local variables:
691  * c-basic-offset: 4
692  * indent-tabs-mode: nil
693  * End:
694  * vim: shiftwidth=4 tabstop=8 expandtab
695  */
696