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