CQL v1.2 functionality except sort.
[yaz-moved-to-github.git] / src / cqltransform.c
1 /* $Id: cqltransform.c,v 1.31 2008-01-06 13:08:09 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         /* we have some aliases for some relations unfortunately.. */
189         if (!res && !prefix && !strcmp(category, "relation"))
190         {
191             if (!strcmp(val, "=="))
192                 res = cql_lookup_property(ct, category, prefix, "exact");
193             if (!strcmp(val, "="))
194                 res = cql_lookup_property(ct, category, prefix, "eq");
195             if (!strcmp(val, "<="))
196                 res = cql_lookup_property(ct, category, prefix, "le");
197             if (!strcmp(val, ">="))
198                 res = cql_lookup_property(ct, category, prefix, "ge");
199         }
200         if (!res)
201             res = cql_lookup_property(ct, category, prefix, "*");
202     }
203     if (res)
204     {
205         char buf[64];
206
207         const char *cp0 = res, *cp1;
208         while ((cp1 = strchr(cp0, '=')))
209         {
210             int i;
211             while (*cp1 && *cp1 != ' ')
212                 cp1++;
213             if (cp1 - cp0 >= sizeof(buf))
214                 break;
215             memcpy (buf, cp0, cp1 - cp0);
216             buf[cp1-cp0] = 0;
217             (*pr)("@attr ", client_data);
218
219             for (i = 0; buf[i]; i++)
220             {
221                 if (buf[i] == '*')
222                     (*pr)(eval, client_data);
223                 else
224                 {
225                     char tmp[2];
226                     tmp[0] = buf[i];
227                     tmp[1] = '\0';
228                     (*pr)(tmp, client_data);
229                 }
230             }
231             (*pr)(" ", client_data);
232             cp0 = cp1;
233             while (*cp0 == ' ')
234                 cp0++;
235         }
236         return 1;
237     }
238     /* error ... */
239     if (errcode && !ct->error)
240     {
241         ct->error = errcode;
242         if (val)
243             ct->addinfo = xstrdup(val);
244         else
245             ct->addinfo = 0;
246     }
247     return 0;
248 }
249
250 int cql_pr_attr(cql_transform_t ct, const char *category,
251                 const char *val, const char *default_val,
252                 void (*pr)(const char *buf, void *client_data),
253                 void *client_data,
254                 int errcode)
255 {
256     return cql_pr_attr_uri(ct, category, 0 /* uri */,
257                            val, default_val, pr, client_data, errcode);
258 }
259
260
261 static void cql_pr_int (int val,
262                         void (*pr)(const char *buf, void *client_data),
263                         void *client_data)
264 {
265     char buf[21];              /* enough characters to 2^64 */
266     sprintf(buf, "%d", val);
267     (*pr)(buf, client_data);
268     (*pr)(" ", client_data);
269 }
270
271
272 static int cql_pr_prox(cql_transform_t ct, struct cql_node *mods,
273                        void (*pr)(const char *buf, void *client_data),
274                        void *client_data)
275 {
276     int exclusion = 0;
277     int distance;               /* to be filled in later depending on unit */
278     int distance_defined = 0;
279     int ordered = 0;
280     int proxrel = 2;            /* less than or equal */
281     int unit = 2;               /* word */
282
283     while (mods != 0) {
284         char *name = mods->u.st.index;
285         char *term = mods->u.st.term;
286         char *relation = mods->u.st.relation;
287
288         if (!strcmp(name, "distance")) {
289             distance = strtol(term, (char**) 0, 0);
290             distance_defined = 1;
291             if (!strcmp(relation, "=")) {
292                 proxrel = 3;
293             } else if (!strcmp(relation, ">")) {
294                 proxrel = 5;
295             } else if (!strcmp(relation, "<")) {
296                 proxrel = 1;
297             } else if (!strcmp(relation, ">=")) {
298                 proxrel = 4;
299             } else if (!strcmp(relation, "<=")) {
300                 proxrel = 2;
301             } else if (!strcmp(relation, "<>")) {
302                 proxrel = 6;
303             } else {
304                 ct->error = 40; /* Unsupported proximity relation */
305                 ct->addinfo = xstrdup(relation);
306                 return 0;
307             }
308         } else if (!strcmp(name, "ordered")) {
309             ordered = 1;
310         } else if (!strcmp(name, "unordered")) {
311             ordered = 0;
312         } else if (!strcmp(name, "unit")) {
313             if (!strcmp(term, "word")) {
314                 unit = 2;
315             } else if (!strcmp(term, "sentence")) {
316                 unit = 3;
317             } else if (!strcmp(term, "paragraph")) {
318                 unit = 4;
319             } else if (!strcmp(term, "element")) {
320                 unit = 8;
321             } else {
322                 ct->error = 42; /* Unsupported proximity unit */
323                 ct->addinfo = xstrdup(term);
324                 return 0;
325             }
326         } else {
327             ct->error = 46;     /* Unsupported boolean modifier */
328             ct->addinfo = xstrdup(name);
329             return 0;
330         }
331
332         mods = mods->u.st.modifiers;
333     }
334
335     if (!distance_defined)
336         distance = (unit == 2) ? 1 : 0;
337
338     cql_pr_int(exclusion, pr, client_data);
339     cql_pr_int(distance, pr, client_data);
340     cql_pr_int(ordered, pr, client_data);
341     cql_pr_int(proxrel, pr, client_data);
342     (*pr)("k ", client_data);
343     cql_pr_int(unit, pr, client_data);
344
345     return 1;
346 }
347
348 /* Returns location of first wildcard character in the `length'
349  * characters starting at `term', or a null pointer of there are
350  * none -- like memchr().
351  */
352 static const char *wcchar(int start, const char *term, int length)
353 {
354     while (length > 0)
355     {
356         if (start || term[-1] != '\\')
357             if (strchr("*?", *term))
358                 return term;
359         term++;
360         length--;
361         start = 0;
362     }
363     return 0;
364 }
365
366
367 /* ### checks for CQL relation-name rather than Type-1 attribute */
368 static int has_modifier(struct cql_node *cn, const char *name) {
369     struct cql_node *mod;
370     for (mod = cn->u.st.modifiers; mod != 0; mod = mod->u.st.modifiers) {
371         if (!strcmp(mod->u.st.index, name))
372             return 1;
373     }
374
375     return 0;
376 }
377
378
379 void emit_term(cql_transform_t ct,
380                struct cql_node *cn,
381                const char *term, int length,
382                void (*pr)(const char *buf, void *client_data),
383                void *client_data)
384 {
385     int i;
386     const char *ns = cn->u.st.index_uri;
387     int process_term = !has_modifier(cn, "regexp");
388     char *z3958_mem = 0;
389
390     assert(cn->which == CQL_NODE_ST);
391
392     if (process_term && length > 0)
393     {
394         if (length > 1 && term[0] == '^' && term[length-1] == '^')
395         {
396             cql_pr_attr(ct, "position", "firstAndLast", 0,
397                         pr, client_data, 32);
398             term++;
399             length -= 2;
400         }
401         else if (term[0] == '^')
402         {
403             cql_pr_attr(ct, "position", "first", 0,
404                         pr, client_data, 32);
405             term++;
406             length--;
407         }
408         else if (term[length-1] == '^')
409         {
410             cql_pr_attr(ct, "position", "last", 0,
411                         pr, client_data, 32);
412             length--;
413         }
414         else
415         {
416             cql_pr_attr(ct, "position", "any", 0,
417                         pr, client_data, 32);
418         }
419     }
420
421     if (process_term && length > 0)
422     {
423         const char *first_wc = wcchar(1, term, length);
424         const char *second_wc = first_wc ?
425             wcchar(0, first_wc+1, length-(first_wc-term)-1) : 0;
426
427         /* Check for well-known globbing patterns that represent
428          * simple truncation attributes as expected by, for example,
429          * Bath-compliant server.  If we find such a pattern but
430          * there's no mapping for it, that's fine: we just use a
431          * general pattern-matching attribute.
432          */
433         if (first_wc == term && second_wc == term + length-1 
434             && *first_wc == '*' && *second_wc == '*' 
435             && cql_pr_attr(ct, "truncation", "both", 0, pr, client_data, 0)) 
436         {
437             term++;
438             length -= 2;
439         }
440         else if (first_wc == term && second_wc == 0 && *first_wc == '*'
441                  && cql_pr_attr(ct, "truncation", "left", 0,
442                                 pr, client_data, 0))
443         {
444             term++;
445             length--;
446         }
447         else if (first_wc == term + length-1 && second_wc == 0
448                  && *first_wc == '*'
449                  && cql_pr_attr(ct, "truncation", "right", 0, 
450                                 pr, client_data, 0))
451         {
452             length--;
453         }
454         else if (first_wc)
455         {
456             /* We have one or more wildcard characters, but not in a
457              * way that can be dealt with using only the standard
458              * left-, right- and both-truncation attributes.  We need
459              * to translate the pattern into a Z39.58-type pattern,
460              * which has been supported in BIB-1 since 1996.  If
461              * there's no configuration element for "truncation.z3958"
462              * we indicate this as error 28 "Masking character not
463              * supported".
464              */
465             int i;
466             cql_pr_attr(ct, "truncation", "z3958", 0,
467                         pr, client_data, 28);
468             z3958_mem = (char *) xmalloc(length+1);
469             for (i = 0; i < length; i++)
470             {
471                 if (i > 0 && term[i-1] == '\\')
472                     z3958_mem[i] = term[i];
473                 else if (term[i] == '*')
474                     z3958_mem[i] = '?';
475                 else if (term[i] == '?')
476                     z3958_mem[i] = '#';
477                 else
478                     z3958_mem[i] = term[i];
479             }
480             z3958_mem[length] = '\0';
481             term = z3958_mem;
482         }
483         else {
484             /* No masking characters.  Use "truncation.none" if given. */
485             cql_pr_attr(ct, "truncation", "none", 0,
486                         pr, client_data, 0);
487         }
488     }
489     if (ns) {
490         cql_pr_attr_uri(ct, "index", ns,
491                         cn->u.st.index, "serverChoice",
492                         pr, client_data, 16);
493     }
494     if (cn->u.st.modifiers)
495     {
496         struct cql_node *mod = cn->u.st.modifiers;
497         for (; mod; mod = mod->u.st.modifiers)
498         {
499             cql_pr_attr(ct, "relationModifier", mod->u.st.index, 0,
500                         pr, client_data, 20);
501         }
502     }
503
504     (*pr)("\"", client_data);
505     for (i = 0; i<length; i++)
506     {
507         /* pr(int) each character */
508         /* we do not need to deal with \-sequences because the
509            CQL and PQF terms have same \-format, bug #1988 */
510         char buf[2];
511
512         buf[0] = term[i];
513         buf[1] = '\0';
514         (*pr)(buf, client_data);
515     }
516     (*pr)("\" ", client_data);
517     xfree(z3958_mem);
518 }
519
520 void emit_wordlist(cql_transform_t ct,
521                    struct cql_node *cn,
522                    void (*pr)(const char *buf, void *client_data),
523                    void *client_data,
524                    const char *op)
525 {
526     const char *cp0 = cn->u.st.term;
527     const char *cp1;
528     const char *last_term = 0;
529     int last_length = 0;
530     while(cp0)
531     {
532         while (*cp0 == ' ')
533             cp0++;
534         cp1 = strchr(cp0, ' ');
535         if (last_term)
536         {
537             (*pr)("@", client_data);
538             (*pr)(op, client_data);
539             (*pr)(" ", client_data);
540             emit_term(ct, cn, last_term, last_length, pr, client_data);
541         }
542         last_term = cp0;
543         if (cp1)
544             last_length = cp1 - cp0;
545         else
546             last_length = strlen(cp0);
547         cp0 = cp1;
548     }
549     if (last_term)
550         emit_term(ct, cn, last_term, last_length, pr, client_data);
551 }
552
553 void cql_transform_r(cql_transform_t ct,
554                      struct cql_node *cn,
555                      void (*pr)(const char *buf, void *client_data),
556                      void *client_data)
557 {
558     const char *ns;
559     struct cql_node *mods;
560
561     if (!cn)
562         return;
563     switch (cn->which)
564     {
565     case CQL_NODE_ST:
566         ns = cn->u.st.index_uri;
567         if (ns)
568         {
569             if (!strcmp(ns, cql_uri())
570                 && cn->u.st.index && !cql_strcmp(cn->u.st.index, "resultSet"))
571             {
572                 (*pr)("@set \"", client_data);
573                 (*pr)(cn->u.st.term, client_data);
574                 (*pr)("\" ", client_data);
575                 return ;
576             }
577         }
578         else
579         {
580             if (!ct->error)
581             {
582                 ct->error = 15;
583                 ct->addinfo = 0;
584             }
585         }
586         cql_pr_attr(ct, "always", 0, 0, pr, client_data, 0);
587         cql_pr_attr(ct, "relation", cn->u.st.relation, 0, pr, client_data, 19);
588         cql_pr_attr(ct, "structure", cn->u.st.relation, 0,
589                     pr, client_data, 24);
590         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "all"))
591         {
592             emit_wordlist(ct, cn, pr, client_data, "and");
593         }
594         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "any"))
595         {
596             emit_wordlist(ct, cn, pr, client_data, "or");
597         }
598         else
599         {
600             emit_term(ct, cn, cn->u.st.term, strlen(cn->u.st.term),
601                       pr, client_data);
602         }
603         break;
604     case CQL_NODE_BOOL:
605         (*pr)("@", client_data);
606         (*pr)(cn->u.boolean.value, client_data);
607         (*pr)(" ", client_data);
608         mods = cn->u.boolean.modifiers;
609         if (!strcmp(cn->u.boolean.value, "prox")) {
610             if (!cql_pr_prox(ct, mods, pr, client_data))
611                 return;
612         } else if (mods) {
613             /* Boolean modifiers other than on proximity not supported */
614             ct->error = 46; /* SRW diag: "Unsupported boolean modifier" */
615             ct->addinfo = xstrdup(mods->u.st.index);
616             return;
617         }
618
619         cql_transform_r(ct, cn->u.boolean.left, pr, client_data);
620         cql_transform_r(ct, cn->u.boolean.right, pr, client_data);
621         break;
622
623     default:
624         fprintf(stderr, "Fatal: impossible CQL node-type %d\n", cn->which);
625         abort();
626     }
627 }
628
629 int cql_transform(cql_transform_t ct,
630                   struct cql_node *cn,
631                   void (*pr)(const char *buf, void *client_data),
632                   void *client_data)
633 {
634     struct cql_prop_entry *e;
635     NMEM nmem = nmem_create();
636
637     ct->error = 0;
638     if (ct->addinfo)
639         xfree (ct->addinfo);
640     ct->addinfo = 0;
641
642     for (e = ct->entry; e ; e = e->next)
643     {
644         if (!cql_strncmp(e->pattern, "set.", 4))
645             cql_apply_prefix(nmem, cn, e->pattern+4, e->value);
646         else if (!cql_strcmp(e->pattern, "set"))
647             cql_apply_prefix(nmem, cn, 0, e->value);
648     }
649     cql_transform_r (ct, cn, pr, client_data);
650     nmem_destroy(nmem);
651     return ct->error;
652 }
653
654
655 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
656 {
657     return cql_transform(ct, cn, cql_fputs, f);
658 }
659
660 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
661                       char *out, int max)
662 {
663     struct cql_buf_write_info info;
664     int r;
665
666     info.off = 0;
667     info.max = max;
668     info.buf = out;
669     r = cql_transform(ct, cn, cql_buf_write_handler, &info);
670     if (info.off < 0) {
671         /* Attempt to write past end of buffer.  For some reason, this
672            SRW diagnostic is deprecated, but it's so perfect for our
673            purposes that it would be stupid not to use it. */
674         char numbuf[30];
675         ct->error = YAZ_SRW_TOO_MANY_CHARS_IN_QUERY;
676         sprintf(numbuf, "%ld", (long) info.max);
677         ct->addinfo = xstrdup(numbuf);
678         return -1;
679     }
680     if (info.off >= 0)
681         info.buf[info.off] = '\0';
682     return r;
683 }
684
685 int cql_transform_error(cql_transform_t ct, const char **addinfo)
686 {
687     *addinfo = ct->addinfo;
688     return ct->error;
689 }
690 /*
691  * Local variables:
692  * c-basic-offset: 4
693  * indent-tabs-mode: nil
694  * End:
695  * vim: shiftwidth=4 tabstop=8 expandtab
696  */
697