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