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