Update source headers for 2008. Omit CVS ID keyword subst.
[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     if (ct->addinfo)
115         xfree (ct->addinfo);
116     xfree (ct);
117 }
118
119 cql_transform_t cql_transform_open_fname(const char *fname)
120 {
121     cql_transform_t ct;
122     FILE *f = fopen(fname, "r");
123     if (!f)
124         return 0;
125     ct = cql_transform_open_FILE(f);
126     fclose(f);
127     return ct;
128 }
129
130 static const char *cql_lookup_property(cql_transform_t ct,
131                                        const char *pat1, const char *pat2,
132                                        const char *pat3)
133 {
134     char pattern[120];
135     struct cql_prop_entry *e;
136
137     if (pat1 && pat2 && pat3)
138         sprintf (pattern, "%.39s.%.39s.%.39s", pat1, pat2, pat3);
139     else if (pat1 && pat2)
140         sprintf (pattern, "%.39s.%.39s", pat1, pat2);
141     else if (pat1 && pat3)
142         sprintf (pattern, "%.39s.%.39s", pat1, pat3);
143     else if (pat1)
144         sprintf (pattern, "%.39s", pat1);
145     else
146         return 0;
147     
148     for (e = ct->entry; e; e = e->next)
149     {
150         if (!cql_strcmp(e->pattern, pattern))
151             return e->value;
152     }
153     return 0;
154 }
155
156 int cql_pr_attr_uri(cql_transform_t ct, const char *category,
157                    const char *uri, const char *val, const char *default_val,
158                    void (*pr)(const char *buf, void *client_data),
159                    void *client_data,
160                    int errcode)
161 {
162     const char *res = 0;
163     const char *eval = val ? val : default_val;
164     const char *prefix = 0;
165     
166     if (uri)
167     {
168         struct cql_prop_entry *e;
169         
170         for (e = ct->entry; e; e = e->next)
171             if (!memcmp(e->pattern, "set.", 4) && e->value &&
172                 !strcmp(e->value, uri))
173             {
174                 prefix = e->pattern+4;
175                 break;
176             }
177         /* must have a prefix now - if not it's an error */
178     }
179
180     if (!uri || prefix)
181     {
182         if (!res)
183             res = cql_lookup_property(ct, category, prefix, eval);
184         /* we have some aliases for some relations unfortunately.. */
185         if (!res && !prefix && !strcmp(category, "relation"))
186         {
187             if (!strcmp(val, "=="))
188                 res = cql_lookup_property(ct, category, prefix, "exact");
189             if (!strcmp(val, "="))
190                 res = cql_lookup_property(ct, category, prefix, "eq");
191             if (!strcmp(val, "<="))
192                 res = cql_lookup_property(ct, category, prefix, "le");
193             if (!strcmp(val, ">="))
194                 res = cql_lookup_property(ct, category, prefix, "ge");
195         }
196         if (!res)
197             res = cql_lookup_property(ct, category, prefix, "*");
198     }
199     if (res)
200     {
201         char buf[64];
202
203         const char *cp0 = res, *cp1;
204         while ((cp1 = strchr(cp0, '=')))
205         {
206             int i;
207             while (*cp1 && *cp1 != ' ')
208                 cp1++;
209             if (cp1 - cp0 >= sizeof(buf))
210                 break;
211             memcpy (buf, cp0, cp1 - cp0);
212             buf[cp1-cp0] = 0;
213             (*pr)("@attr ", client_data);
214
215             for (i = 0; buf[i]; i++)
216             {
217                 if (buf[i] == '*')
218                     (*pr)(eval, client_data);
219                 else
220                 {
221                     char tmp[2];
222                     tmp[0] = buf[i];
223                     tmp[1] = '\0';
224                     (*pr)(tmp, client_data);
225                 }
226             }
227             (*pr)(" ", client_data);
228             cp0 = cp1;
229             while (*cp0 == ' ')
230                 cp0++;
231         }
232         return 1;
233     }
234     /* error ... */
235     if (errcode && !ct->error)
236     {
237         ct->error = errcode;
238         if (val)
239             ct->addinfo = xstrdup(val);
240         else
241             ct->addinfo = 0;
242     }
243     return 0;
244 }
245
246 int cql_pr_attr(cql_transform_t ct, const char *category,
247                 const char *val, const char *default_val,
248                 void (*pr)(const char *buf, void *client_data),
249                 void *client_data,
250                 int errcode)
251 {
252     return cql_pr_attr_uri(ct, category, 0 /* uri */,
253                            val, default_val, pr, client_data, errcode);
254 }
255
256
257 static void cql_pr_int (int val,
258                         void (*pr)(const char *buf, void *client_data),
259                         void *client_data)
260 {
261     char buf[21];              /* enough characters to 2^64 */
262     sprintf(buf, "%d", val);
263     (*pr)(buf, client_data);
264     (*pr)(" ", client_data);
265 }
266
267
268 static int cql_pr_prox(cql_transform_t ct, struct cql_node *mods,
269                        void (*pr)(const char *buf, void *client_data),
270                        void *client_data)
271 {
272     int exclusion = 0;
273     int distance;               /* to be filled in later depending on unit */
274     int distance_defined = 0;
275     int ordered = 0;
276     int proxrel = 2;            /* less than or equal */
277     int unit = 2;               /* word */
278
279     while (mods != 0) {
280         char *name = mods->u.st.index;
281         char *term = mods->u.st.term;
282         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                 ct->error = 40; /* Unsupported proximity relation */
301                 ct->addinfo = xstrdup(relation);
302                 return 0;
303             }
304         } else if (!strcmp(name, "ordered")) {
305             ordered = 1;
306         } else if (!strcmp(name, "unordered")) {
307             ordered = 0;
308         } else if (!strcmp(name, "unit")) {
309             if (!strcmp(term, "word")) {
310                 unit = 2;
311             } else if (!strcmp(term, "sentence")) {
312                 unit = 3;
313             } else if (!strcmp(term, "paragraph")) {
314                 unit = 4;
315             } else if (!strcmp(term, "element")) {
316                 unit = 8;
317             } else {
318                 ct->error = 42; /* Unsupported proximity unit */
319                 ct->addinfo = xstrdup(term);
320                 return 0;
321             }
322         } else {
323             ct->error = 46;     /* Unsupported boolean modifier */
324             ct->addinfo = xstrdup(name);
325             return 0;
326         }
327
328         mods = mods->u.st.modifiers;
329     }
330
331     if (!distance_defined)
332         distance = (unit == 2) ? 1 : 0;
333
334     cql_pr_int(exclusion, pr, client_data);
335     cql_pr_int(distance, pr, client_data);
336     cql_pr_int(ordered, pr, client_data);
337     cql_pr_int(proxrel, pr, client_data);
338     (*pr)("k ", client_data);
339     cql_pr_int(unit, pr, client_data);
340
341     return 1;
342 }
343
344 /* Returns location of first wildcard character in the `length'
345  * characters starting at `term', or a null pointer of there are
346  * none -- like memchr().
347  */
348 static const char *wcchar(int start, const char *term, int length)
349 {
350     while (length > 0)
351     {
352         if (start || term[-1] != '\\')
353             if (strchr("*?", *term))
354                 return term;
355         term++;
356         length--;
357         start = 0;
358     }
359     return 0;
360 }
361
362
363 /* ### checks for CQL relation-name rather than Type-1 attribute */
364 static int has_modifier(struct cql_node *cn, const char *name) {
365     struct cql_node *mod;
366     for (mod = cn->u.st.modifiers; mod != 0; mod = mod->u.st.modifiers) {
367         if (!strcmp(mod->u.st.index, name))
368             return 1;
369     }
370
371     return 0;
372 }
373
374
375 void emit_term(cql_transform_t ct,
376                struct cql_node *cn,
377                const char *term, int length,
378                void (*pr)(const char *buf, void *client_data),
379                void *client_data)
380 {
381     int i;
382     const char *ns = cn->u.st.index_uri;
383     int process_term = !has_modifier(cn, "regexp");
384     char *z3958_mem = 0;
385
386     assert(cn->which == CQL_NODE_ST);
387
388     if (process_term && length > 0)
389     {
390         if (length > 1 && term[0] == '^' && term[length-1] == '^')
391         {
392             cql_pr_attr(ct, "position", "firstAndLast", 0,
393                         pr, client_data, 32);
394             term++;
395             length -= 2;
396         }
397         else if (term[0] == '^')
398         {
399             cql_pr_attr(ct, "position", "first", 0,
400                         pr, client_data, 32);
401             term++;
402             length--;
403         }
404         else if (term[length-1] == '^')
405         {
406             cql_pr_attr(ct, "position", "last", 0,
407                         pr, client_data, 32);
408             length--;
409         }
410         else
411         {
412             cql_pr_attr(ct, "position", "any", 0,
413                         pr, client_data, 32);
414         }
415     }
416
417     if (process_term && length > 0)
418     {
419         const char *first_wc = wcchar(1, term, length);
420         const char *second_wc = first_wc ?
421             wcchar(0, first_wc+1, length-(first_wc-term)-1) : 0;
422
423         /* Check for well-known globbing patterns that represent
424          * simple truncation attributes as expected by, for example,
425          * Bath-compliant server.  If we find such a pattern but
426          * there's no mapping for it, that's fine: we just use a
427          * general pattern-matching attribute.
428          */
429         if (first_wc == term && second_wc == term + length-1 
430             && *first_wc == '*' && *second_wc == '*' 
431             && cql_pr_attr(ct, "truncation", "both", 0, pr, client_data, 0)) 
432         {
433             term++;
434             length -= 2;
435         }
436         else if (first_wc == term && second_wc == 0 && *first_wc == '*'
437                  && cql_pr_attr(ct, "truncation", "left", 0,
438                                 pr, client_data, 0))
439         {
440             term++;
441             length--;
442         }
443         else if (first_wc == term + length-1 && second_wc == 0
444                  && *first_wc == '*'
445                  && cql_pr_attr(ct, "truncation", "right", 0, 
446                                 pr, client_data, 0))
447         {
448             length--;
449         }
450         else if (first_wc)
451         {
452             /* We have one or more wildcard characters, but not in a
453              * way that can be dealt with using only the standard
454              * left-, right- and both-truncation attributes.  We need
455              * to translate the pattern into a Z39.58-type pattern,
456              * which has been supported in BIB-1 since 1996.  If
457              * there's no configuration element for "truncation.z3958"
458              * we indicate this as error 28 "Masking character not
459              * supported".
460              */
461             int i;
462             cql_pr_attr(ct, "truncation", "z3958", 0,
463                         pr, client_data, 28);
464             z3958_mem = (char *) xmalloc(length+1);
465             for (i = 0; i < length; i++)
466             {
467                 if (i > 0 && term[i-1] == '\\')
468                     z3958_mem[i] = term[i];
469                 else if (term[i] == '*')
470                     z3958_mem[i] = '?';
471                 else if (term[i] == '?')
472                     z3958_mem[i] = '#';
473                 else
474                     z3958_mem[i] = term[i];
475             }
476             z3958_mem[length] = '\0';
477             term = z3958_mem;
478         }
479         else {
480             /* No masking characters.  Use "truncation.none" if given. */
481             cql_pr_attr(ct, "truncation", "none", 0,
482                         pr, client_data, 0);
483         }
484     }
485     if (ns) {
486         cql_pr_attr_uri(ct, "index", ns,
487                         cn->u.st.index, "serverChoice",
488                         pr, client_data, 16);
489     }
490     if (cn->u.st.modifiers)
491     {
492         struct cql_node *mod = cn->u.st.modifiers;
493         for (; mod; mod = mod->u.st.modifiers)
494         {
495             cql_pr_attr(ct, "relationModifier", mod->u.st.index, 0,
496                         pr, client_data, 20);
497         }
498     }
499
500     (*pr)("\"", client_data);
501     for (i = 0; i<length; i++)
502     {
503         /* pr(int) each character */
504         /* we do not need to deal with \-sequences because the
505            CQL and PQF terms have same \-format, bug #1988 */
506         char buf[2];
507
508         buf[0] = term[i];
509         buf[1] = '\0';
510         (*pr)(buf, client_data);
511     }
512     (*pr)("\" ", client_data);
513     xfree(z3958_mem);
514 }
515
516 void emit_terms(cql_transform_t ct,
517                 struct cql_node *cn,
518                 void (*pr)(const char *buf, void *client_data),
519                 void *client_data,
520                 const char *op)
521 {
522     struct cql_node *ne = cn->u.st.extra_terms;
523     if (ne)
524     {
525         (*pr)("@", client_data);
526         (*pr)(op, client_data);
527         (*pr)(" ", client_data);
528     }
529     emit_term(ct, cn, cn->u.st.term, strlen(cn->u.st.term),
530               pr, client_data);
531     for (; ne; ne = ne->u.st.extra_terms)
532     {
533         if (ne->u.st.extra_terms)
534         {
535             (*pr)("@", client_data);
536             (*pr)(op, client_data);
537             (*pr)(" ", client_data);
538         }            
539         emit_term(ct, cn, ne->u.st.term, strlen(ne->u.st.term),
540                   pr, client_data);
541     }
542 }
543
544 void emit_wordlist(cql_transform_t ct,
545                    struct cql_node *cn,
546                    void (*pr)(const char *buf, void *client_data),
547                    void *client_data,
548                    const char *op)
549 {
550     const char *cp0 = cn->u.st.term;
551     const char *cp1;
552     const char *last_term = 0;
553     int last_length = 0;
554     while(cp0)
555     {
556         while (*cp0 == ' ')
557             cp0++;
558         cp1 = strchr(cp0, ' ');
559         if (last_term)
560         {
561             (*pr)("@", client_data);
562             (*pr)(op, client_data);
563             (*pr)(" ", client_data);
564             emit_term(ct, cn, last_term, last_length, pr, client_data);
565         }
566         last_term = cp0;
567         if (cp1)
568             last_length = cp1 - cp0;
569         else
570             last_length = strlen(cp0);
571         cp0 = cp1;
572     }
573     if (last_term)
574         emit_term(ct, cn, last_term, last_length, pr, client_data);
575 }
576
577 void cql_transform_r(cql_transform_t ct,
578                      struct cql_node *cn,
579                      void (*pr)(const char *buf, void *client_data),
580                      void *client_data)
581 {
582     const char *ns;
583     struct cql_node *mods;
584
585     if (!cn)
586         return;
587     switch (cn->which)
588     {
589     case CQL_NODE_ST:
590         ns = cn->u.st.index_uri;
591         if (ns)
592         {
593             if (!strcmp(ns, cql_uri())
594                 && cn->u.st.index && !cql_strcmp(cn->u.st.index, "resultSet"))
595             {
596                 (*pr)("@set \"", client_data);
597                 (*pr)(cn->u.st.term, client_data);
598                 (*pr)("\" ", client_data);
599                 return ;
600             }
601         }
602         else
603         {
604             if (!ct->error)
605             {
606                 ct->error = 15;
607                 ct->addinfo = 0;
608             }
609         }
610         cql_pr_attr(ct, "always", 0, 0, pr, client_data, 0);
611         cql_pr_attr(ct, "relation", cn->u.st.relation, 0, pr, client_data, 19);
612         cql_pr_attr(ct, "structure", cn->u.st.relation, 0,
613                     pr, client_data, 24);
614         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "all"))
615         {
616             emit_wordlist(ct, cn, pr, client_data, "and");
617         }
618         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "any"))
619         {
620             emit_wordlist(ct, cn, pr, client_data, "or");
621         }
622         else
623         {
624             emit_terms(ct, cn, pr, client_data, "and");
625         }
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             if (!cql_pr_prox(ct, mods, pr, client_data))
634                 return;
635         } else if (mods) {
636             /* Boolean modifiers other than on proximity not supported */
637             ct->error = 46; /* SRW diag: "Unsupported boolean modifier" */
638             ct->addinfo = xstrdup(mods->u.st.index);
639             return;
640         }
641
642         cql_transform_r(ct, cn->u.boolean.left, pr, client_data);
643         cql_transform_r(ct, cn->u.boolean.right, pr, client_data);
644         break;
645
646     default:
647         fprintf(stderr, "Fatal: impossible CQL node-type %d\n", cn->which);
648         abort();
649     }
650 }
651
652 int cql_transform(cql_transform_t ct,
653                   struct cql_node *cn,
654                   void (*pr)(const char *buf, void *client_data),
655                   void *client_data)
656 {
657     struct cql_prop_entry *e;
658     NMEM nmem = nmem_create();
659
660     ct->error = 0;
661     if (ct->addinfo)
662         xfree (ct->addinfo);
663     ct->addinfo = 0;
664
665     for (e = ct->entry; e ; e = e->next)
666     {
667         if (!cql_strncmp(e->pattern, "set.", 4))
668             cql_apply_prefix(nmem, cn, e->pattern+4, e->value);
669         else if (!cql_strcmp(e->pattern, "set"))
670             cql_apply_prefix(nmem, cn, 0, e->value);
671     }
672     cql_transform_r (ct, cn, pr, client_data);
673     nmem_destroy(nmem);
674     return ct->error;
675 }
676
677
678 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
679 {
680     return cql_transform(ct, cn, cql_fputs, f);
681 }
682
683 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
684                       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