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