Fixed bug #1122: Certain record IDs are not found by searches.
[yaz-moved-to-github.git] / src / cqltransform.c
1 /* $Id: cqltransform.c,v 1.29 2007-10-31 21:58:07 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         char buf[3];
497         const char *cp;
498
499         buf[1] = term[i];
500         buf[2] = 0;
501         /* do we have to escape this char? */
502         if (buf[1] == '"')
503         {
504             buf[0] = '\\';
505             cp = buf;
506         }
507         else
508             cp = buf+1;
509         (*pr)(cp, client_data);
510     }
511     (*pr)("\" ", client_data);
512     xfree(z3958_mem);
513 }
514
515 void emit_wordlist(cql_transform_t ct,
516                    struct cql_node *cn,
517                    void (*pr)(const char *buf, void *client_data),
518                    void *client_data,
519                    const char *op)
520 {
521     const char *cp0 = cn->u.st.term;
522     const char *cp1;
523     const char *last_term = 0;
524     int last_length = 0;
525     while(cp0)
526     {
527         while (*cp0 == ' ')
528             cp0++;
529         cp1 = strchr(cp0, ' ');
530         if (last_term)
531         {
532             (*pr)("@", client_data);
533             (*pr)(op, client_data);
534             (*pr)(" ", client_data);
535             emit_term(ct, cn, last_term, last_length, pr, client_data);
536         }
537         last_term = cp0;
538         if (cp1)
539             last_length = cp1 - cp0;
540         else
541             last_length = strlen(cp0);
542         cp0 = cp1;
543     }
544     if (last_term)
545         emit_term(ct, cn, last_term, last_length, pr, client_data);
546 }
547
548 void cql_transform_r(cql_transform_t ct,
549                      struct cql_node *cn,
550                      void (*pr)(const char *buf, void *client_data),
551                      void *client_data)
552 {
553     const char *ns;
554     struct cql_node *mods;
555
556     if (!cn)
557         return;
558     switch (cn->which)
559     {
560     case CQL_NODE_ST:
561         ns = cn->u.st.index_uri;
562         if (ns)
563         {
564             if (!strcmp(ns, cql_uri())
565                 && cn->u.st.index && !cql_strcmp(cn->u.st.index, "resultSet"))
566             {
567                 (*pr)("@set \"", client_data);
568                 (*pr)(cn->u.st.term, client_data);
569                 (*pr)("\" ", client_data);
570                 return ;
571             }
572         }
573         else
574         {
575             if (!ct->error)
576             {
577                 ct->error = 15;
578                 ct->addinfo = 0;
579             }
580         }
581         cql_pr_attr(ct, "always", 0, 0, pr, client_data, 0);
582         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "="))
583             cql_pr_attr(ct, "relation", "eq", "scr",
584                         pr, client_data, 19);
585         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "<="))
586             cql_pr_attr(ct, "relation", "le", "scr",
587                         pr, client_data, 19);
588         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, ">="))
589             cql_pr_attr(ct, "relation", "ge", "scr",
590                         pr, client_data, 19);
591         else
592             cql_pr_attr(ct, "relation", cn->u.st.relation, "eq",
593                         pr, client_data, 19);
594         cql_pr_attr(ct, "structure", cn->u.st.relation, 0,
595                     pr, client_data, 24);
596         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "all"))
597         {
598             emit_wordlist(ct, cn, pr, client_data, "and");
599         }
600         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "any"))
601         {
602             emit_wordlist(ct, cn, pr, client_data, "or");
603         }
604         else
605         {
606             emit_term(ct, cn, cn->u.st.term, strlen(cn->u.st.term),
607                       pr, client_data);
608         }
609         break;
610     case CQL_NODE_BOOL:
611         (*pr)("@", client_data);
612         (*pr)(cn->u.boolean.value, client_data);
613         (*pr)(" ", client_data);
614         mods = cn->u.boolean.modifiers;
615         if (!strcmp(cn->u.boolean.value, "prox")) {
616             if (!cql_pr_prox(ct, mods, pr, client_data))
617                 return;
618         } else if (mods) {
619             /* Boolean modifiers other than on proximity not supported */
620             ct->error = 46; /* SRW diag: "Unsupported boolean modifier" */
621             ct->addinfo = xstrdup(mods->u.st.index);
622             return;
623         }
624
625         cql_transform_r(ct, cn->u.boolean.left, pr, client_data);
626         cql_transform_r(ct, cn->u.boolean.right, pr, client_data);
627         break;
628
629     default:
630         fprintf(stderr, "Fatal: impossible CQL node-type %d\n", cn->which);
631         abort();
632     }
633 }
634
635 int cql_transform(cql_transform_t ct,
636                   struct cql_node *cn,
637                   void (*pr)(const char *buf, void *client_data),
638                   void *client_data)
639 {
640     struct cql_prop_entry *e;
641     NMEM nmem = nmem_create();
642
643     ct->error = 0;
644     if (ct->addinfo)
645         xfree (ct->addinfo);
646     ct->addinfo = 0;
647
648     for (e = ct->entry; e ; e = e->next)
649     {
650         if (!cql_strncmp(e->pattern, "set.", 4))
651             cql_apply_prefix(nmem, cn, e->pattern+4, e->value);
652         else if (!cql_strcmp(e->pattern, "set"))
653             cql_apply_prefix(nmem, cn, 0, e->value);
654     }
655     cql_transform_r (ct, cn, pr, client_data);
656     nmem_destroy(nmem);
657     return ct->error;
658 }
659
660
661 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
662 {
663     return cql_transform(ct, cn, cql_fputs, f);
664 }
665
666 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
667                       char *out, int max)
668 {
669     struct cql_buf_write_info info;
670     int r;
671
672     info.off = 0;
673     info.max = max;
674     info.buf = out;
675     r = cql_transform(ct, cn, cql_buf_write_handler, &info);
676     if (info.off < 0) {
677         /* Attempt to write past end of buffer.  For some reason, this
678            SRW diagnostic is deprecated, but it's so perfect for our
679            purposes that it would be stupid not to use it. */
680         char numbuf[30];
681         ct->error = YAZ_SRW_TOO_MANY_CHARS_IN_QUERY;
682         sprintf(numbuf, "%ld", (long) info.max);
683         ct->addinfo = xstrdup(numbuf);
684         return -1;
685     }
686     if (info.off >= 0)
687         info.buf[info.off] = '\0';
688     return r;
689 }
690
691 int cql_transform_error(cql_transform_t ct, const char **addinfo)
692 {
693     *addinfo = ct->addinfo;
694     return ct->error;
695 }
696 /*
697  * Local variables:
698  * c-basic-offset: 4
699  * indent-tabs-mode: nil
700  * End:
701  * vim: shiftwidth=4 tabstop=8 expandtab
702  */
703