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