22f3fe56c834bc6afba045e66a93450e0748a9cf
[yaz-moved-to-github.git] / src / cqltransform.c
1 /* $Id: cqltransform.c,v 1.6 2003-12-19 12:16:19 mike Exp $
2    Copyright (C) 2002-2003
3    Index Data Aps
4
5 This file is part of the YAZ toolkit.
6
7 See the file LICENSE.
8 */
9
10 #include <stdlib.h>
11 #include <string.h>
12 #include <yaz/cql.h>
13
14 struct cql_prop_entry {
15     char *pattern;
16     char *value;
17     struct cql_prop_entry *next;
18 };
19
20 struct cql_transform_t_ {
21     struct cql_prop_entry *entry;
22     int error;
23     char *addinfo;
24 };
25
26 cql_transform_t cql_transform_open_FILE(FILE *f)
27 {
28     char line[1024];
29     cql_transform_t ct = (cql_transform_t) malloc (sizeof(*ct));
30     struct cql_prop_entry **pp = &ct->entry;
31
32     ct->error = 0;
33     ct->addinfo = 0;
34     while (fgets(line, sizeof(line)-1, f))
35     {
36         const char *cp_value_start;
37         const char *cp_value_end;
38         const char *cp_pattern_end;
39         const char *cp = line;
40         while (*cp && !strchr(" \t=\r\n#", *cp))
41             cp++;
42         cp_pattern_end = cp;
43         if (cp == line)
44             continue;
45         while (*cp && strchr(" \t\r\n", *cp))
46             cp++;
47         if (*cp != '=')
48             continue;
49         cp++;
50         while (*cp && strchr(" \t\r\n", *cp))
51             cp++;
52         cp_value_start = cp;
53         if (!(cp_value_end = strchr(cp, '#')))
54             cp_value_end = strlen(line) + line;
55
56         if (cp_value_end != cp_value_start &&
57             strchr(" \t\r\n", cp_value_end[-1]))
58             cp_value_end--;
59         *pp = (struct cql_prop_entry *) malloc (sizeof(**pp));
60         (*pp)->pattern = (char *) malloc (cp_pattern_end - line + 1);
61         memcpy ((*pp)->pattern, line, cp_pattern_end - line);
62         (*pp)->pattern[cp_pattern_end-line] = 0;
63
64         (*pp)->value = (char *) malloc (cp_value_end - cp_value_start + 1);
65         if (cp_value_start != cp_value_end)
66             memcpy ((*pp)->value, cp_value_start, cp_value_end-cp_value_start);
67         (*pp)->value[cp_value_end - cp_value_start] = 0;
68         pp = &(*pp)->next;
69     }
70     *pp = 0;
71     return ct;
72 }
73
74 void cql_transform_close(cql_transform_t ct)
75 {
76     struct cql_prop_entry *pe;
77     if (!ct)
78         return;
79     pe = ct->entry;
80     while (pe)
81     {
82         struct cql_prop_entry *pe_next = pe->next;
83         free (pe->pattern);
84         free (pe->value);
85         free (pe);
86         pe = pe_next;
87     }
88     if (ct->addinfo)
89         free (ct->addinfo);
90     free (ct);
91 }
92
93 cql_transform_t cql_transform_open_fname(const char *fname)
94 {
95     cql_transform_t ct;
96     FILE *f = fopen(fname, "r");
97     if (!f)
98         return 0;
99     ct = cql_transform_open_FILE(f);
100     fclose(f);
101     return ct;
102 }
103
104 static const char *cql_lookup_property(cql_transform_t ct,
105                                        const char *pat1, const char *pat2)
106 {
107     char pattern[80];
108     struct cql_prop_entry *e;
109
110     if (pat2)
111         sprintf (pattern, "%.39s%.39s", pat1, pat2);
112     else
113         sprintf (pattern, "%.39s", pat1);
114     for (e = ct->entry; e; e = e->next)
115     {
116         if (!strcmp(e->pattern, pattern))
117             return e->value;
118     }
119     return 0;
120 }
121
122 static const char *cql_lookup_value(cql_transform_t ct,
123                                     const char *prefix,
124                                     const char *value)
125 {
126     struct cql_prop_entry *e;
127     int len = strlen(prefix);
128
129     for (e = ct->entry; e; e = e->next)
130     {
131         if (!memcmp(e->pattern, prefix, len) && !strcmp(e->value, value))
132             return e->pattern + len;
133     }
134     return 0;
135 }
136
137
138 int cql_pr_attr(cql_transform_t ct, const char *category,
139                 const char *val,
140                 const char *default_val,
141                 void (*pr)(const char *buf, void *client_data),
142                 void *client_data,
143                 int errcode)
144 {
145     const char *res;
146     res = cql_lookup_property(ct, category, val ? val : default_val);
147     if (!res)
148         res = cql_lookup_property(ct, category, "*");
149     if (res)
150     {
151         char buf[64];
152
153         const char *cp0 = res, *cp1;
154         while ((cp1 = strchr(cp0, '=')))
155         {
156             while (*cp1 && *cp1 != ' ')
157                 cp1++;
158             if (cp1 - cp0 >= sizeof(buf))
159                 break;
160             memcpy (buf, cp0, cp1 - cp0);
161             buf[cp1-cp0] = 0;
162             (*pr)("@attr ", client_data);
163             (*pr)(buf, client_data);
164             (*pr)(" ", client_data);
165             cp0 = cp1;
166             while (*cp0 == ' ')
167                 cp0++;
168         }
169         return 1;
170     }
171     /* error ... */
172     if (errcode && !ct->error)
173     {
174         ct->error = errcode;
175         ct->addinfo = strdup(val);
176     }
177     return 0;
178 }
179
180
181 /* Returns location of first wildcard character in the `length'
182  * characters starting at `term', or a null pointer of there are
183  * none -- like memchr().
184  */
185 static char *wcchar(const char *term, int length)
186 {
187     char *best = 0;
188     char *current;
189     char *whichp;
190
191     for (whichp = "*?"; *whichp != '\0'; whichp++) {
192         current = memchr(term, *whichp, length);
193         if (current != 0 && (best == 0 || current < best))
194             best = current;
195     }
196
197     return best;
198 }
199
200
201 void emit_term(cql_transform_t ct,
202                const char *term, int length,
203                void (*pr)(const char *buf, void *client_data),
204                void *client_data)
205 {
206     int i;
207     if (length > 0)
208     {
209         if (length > 1 && term[0] == '^' && term[length-1] == '^')
210         {
211             cql_pr_attr(ct, "position.", "firstAndLast", 0,
212                         pr, client_data, 32);
213             term++;
214             length -= 2;
215         }
216         else if (term[0] == '^')
217         {
218             cql_pr_attr(ct, "position.", "first", 0,
219                         pr, client_data, 32);
220             term++;
221             length--;
222         }
223         else if (term[length-1] == '^')
224         {
225             cql_pr_attr(ct, "position.", "last", 0,
226                         pr, client_data, 32);
227             length--;
228         }
229         else
230         {
231             cql_pr_attr(ct, "position.", "any", 0,
232                         pr, client_data, 32);
233         }
234     }
235
236     if (length > 0)
237     {
238         /* Check for well-known globbing patterns that represent
239          * simple truncation attributes as expected by, for example,
240          * Bath-compliant server.  If we find such a pattern but
241          * there's no mapping for it, that's fine: we just use a
242          * general pattern-matching attribute.
243          */
244         if (length > 1 && term[0] == '*' && term[length-1] == '*' &&
245             wcchar(term+1, length-2) == 0 &&
246             cql_pr_attr(ct, "truncation.", "both", 0,
247                         pr, client_data, 0)) {
248             term++;
249             length -= 2;
250         }
251         else if (term[0] == '*' &&
252                  wcchar(term+1, length-1) == 0 &&
253                  cql_pr_attr(ct, "truncation.", "left", 0,
254                              pr, client_data, 0)) {
255             term++;
256             length--;
257         }
258         else if (term[length-1] == '*' &&
259                  wcchar(term, length-1) == 0 &&
260                  cql_pr_attr(ct, "truncation.", "right", 0,
261                              pr, client_data, 0)) {
262             length--;
263         }
264         else if (wcchar(term, length))
265         {
266             /* We have one or more wildcard characters, but not in a
267              * way that can be dealt with using only the standard
268              * left-, right- and both-truncation attributes.  We need
269              * to translate the pattern into a Z39.58-type pattern,
270              * which has been supported in BIB-1 since 1996.  If
271              * there's no configuration element for "truncation.z3958"
272              * we indicate this as error 28 "Masking character not
273              * supported".
274              */
275             int i;
276             char *mem;
277             cql_pr_attr(ct, "truncation.", "z3958", 0,
278                         pr, client_data, 28);
279             mem = malloc(length+1);
280             for (i = 0; i < length; i++) {
281                 if (term[i] == '*')      mem[i] = '?';
282                 else if (term[i] == '?') mem[i] = '#';
283                 else                     mem[i] = term[i];
284             }
285             mem[length] = '\0';
286             term = mem;
287         }
288         else {
289             /* No masking characters.  If there's no "truncation.none"
290              * configuration element, that's an error which we
291              * indicate (rather tangentially) as 30 "Too many masking
292              * characters in term".  28 would be equally meaningful
293              * (or meaningless) but using a different value allows us
294              * to differentiate between this case and the previous
295              * one.
296              */
297             cql_pr_attr(ct, "truncation.", "none", 0,
298                         pr, client_data, 30);
299         }
300     }
301
302     (*pr)("\"", client_data);
303     for (i = 0; i<length; i++)
304     {
305         char buf[2];
306         buf[0] = term[i];
307         buf[1] = 0;
308         (*pr)(buf, client_data);
309     }
310     (*pr)("\" ", client_data);
311 }
312
313 void emit_wordlist(cql_transform_t ct,
314                    struct cql_node *cn,
315                    void (*pr)(const char *buf, void *client_data),
316                    void *client_data,
317                    const char *op)
318 {
319     const char *cp0 = cn->u.st.term;
320     const char *cp1;
321     const char *last_term = 0;
322     int last_length = 0;
323     while(cp0)
324     {
325         while (*cp0 == ' ')
326             cp0++;
327         cp1 = strchr(cp0, ' ');
328         if (last_term)
329         {
330             (*pr)("@", client_data);
331             (*pr)(op, client_data);
332             (*pr)(" ", client_data);
333             emit_term(ct, last_term, last_length, pr, client_data);
334         }
335         last_term = cp0;
336         if (cp1)
337             last_length = cp1 - cp0;
338         else
339             last_length = strlen(cp0);
340         cp0 = cp1;
341     }
342     if (last_term)
343         emit_term(ct, last_term, last_length, pr, client_data);
344 }
345
346
347 static const char *cql_get_ns(cql_transform_t ct,
348                               struct cql_node *cn,
349                               struct cql_node **prefix_ar, int prefix_level,
350                               const char **n_prefix,
351                               const char **n_suffix)
352 {
353     int i;
354     const char *ns = 0;
355     char prefix[32];
356     const char *cp = cn->u.st.index;
357     const char *cp_dot = strchr(cp, '.');
358
359     /* strz current prefix (empty if not given) */
360     if (cp_dot && cp_dot-cp < sizeof(prefix))
361     {
362         memcpy (prefix, cp, cp_dot - cp);
363         prefix[cp_dot - cp] = 0;
364     }
365     else
366         *prefix = 0;
367
368     /* 2. lookup in prefix_ar. and return NS */
369     for (i = prefix_level; !ns && --i >= 0; )
370     {
371         struct cql_node *cn_prefix = prefix_ar[i];
372         for (; cn_prefix; cn_prefix = cn_prefix->u.mod.next)
373         {
374             if (*prefix && cn_prefix->u.mod.name &&
375                 !strcmp(prefix, cn_prefix->u.mod.name))
376             {
377                 ns = cn_prefix->u.mod.value;
378                 break;
379             }
380             else if (!*prefix && !cn_prefix->u.mod.name)
381             {
382                 ns = cn_prefix->u.mod.value;
383                 break;
384             }
385         }
386     }
387     if (!ns)
388     {
389         if (!ct->error)
390         {
391             ct->error = 15;
392             ct->addinfo = strdup(prefix);
393         }
394         return 0;
395     }
396     /* 3. lookup in set.NS for new prefix */
397     *n_prefix = cql_lookup_value(ct, "set.", ns);
398     if (!*n_prefix)
399     {
400         if (!ct->error)
401         {
402             ct->error = 15;
403             ct->addinfo = strdup(ns);
404         }
405         return 0;
406     }
407     /* 4. lookup index.prefix. */
408     
409     cp = cn->u.st.index;
410     cp_dot = strchr(cp, '.');
411     
412     *n_suffix = cp_dot ? cp_dot+1 : cp;
413     return ns;
414 }
415
416 void cql_transform_r(cql_transform_t ct,
417                      struct cql_node *cn,
418                      void (*pr)(const char *buf, void *client_data),
419                      void *client_data,
420                      struct cql_node **prefix_ar, int prefix_level)
421 {
422     const char *ns, *n_prefix, *n_suffix;
423
424     if (!cn)
425         return;
426     switch (cn->which)
427     {
428     case CQL_NODE_ST:
429         if (cn->u.st.prefixes && prefix_level < 20)
430             prefix_ar[prefix_level++] = cn->u.st.prefixes;
431         ns = cql_get_ns(ct, cn, prefix_ar, prefix_level, &n_prefix, &n_suffix);
432         if (ns)
433         {
434             char n_full[64];
435             sprintf (n_full, "%.20s.%.40s", n_prefix, n_suffix);
436         
437             if ((!strcmp(ns, "http://www.loc.gov/zing/cql/context-sets/cql/v1.1/") ||
438                  !strcmp(ns, "http://www.loc.gov/zing/cql/srw-indexes/v1.0/"))
439                 && !strcmp(n_suffix, "resultSet"))
440             {
441                 (*pr)("@set \"", client_data);
442                 (*pr)(cn->u.st.term, client_data);
443                 (*pr)("\" ", client_data);
444                 return ;
445             }
446             /* ### It would be nice if this could fall back to whichever 
447                of cql.serverChoice and srw.serverChoice is defined */
448             if (!cql_pr_attr(ct, "index.", n_full, "cql.serverChoice",
449                              pr, client_data, 16)) {
450                 /* No index.foo; reset error and fall back to qualifier.foo */
451                 if (ct->error == 16) ct->error = 0;
452                 cql_pr_attr(ct, "qualifier.", n_full, "cql.serverChoice",
453                             pr, client_data, 16);
454             }
455         }
456
457         if (cn->u.st.relation && !strcmp(cn->u.st.relation, "="))
458             cql_pr_attr(ct, "relation.", "eq", "scr",
459                         pr, client_data, 19);
460         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, "<="))
461             cql_pr_attr(ct, "relation.", "le", "scr",
462                         pr, client_data, 19);
463         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, ">="))
464             cql_pr_attr(ct, "relation.", "ge", "scr",
465                         pr, client_data, 19);
466         else
467             cql_pr_attr(ct, "relation.", cn->u.st.relation, "eq",
468                         pr, client_data, 19);
469         if (cn->u.st.modifiers)
470         {
471             struct cql_node *mod = cn->u.st.modifiers;
472             for (; mod; mod = mod->u.mod.next)
473             {
474                 cql_pr_attr(ct, "relationModifier.", mod->u.mod.value, 0,
475                             pr, client_data, 20);
476             }
477         }
478         cql_pr_attr(ct, "structure.", cn->u.st.relation, 0,
479                     pr, client_data, 24);
480         if (cn->u.st.relation && !strcmp(cn->u.st.relation, "all"))
481         {
482             emit_wordlist(ct, cn, pr, client_data, "and");
483         }
484         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, "any"))
485         {
486             emit_wordlist(ct, cn, pr, client_data, "or");
487         }
488         else
489         {
490             emit_term(ct, cn->u.st.term, strlen(cn->u.st.term),
491                       pr, client_data);
492         }
493         break;
494     case CQL_NODE_BOOL:
495         if (cn->u.boolean.prefixes && prefix_level < 20)
496             prefix_ar[prefix_level++] = cn->u.boolean.prefixes;
497         (*pr)("@", client_data);
498         (*pr)(cn->u.boolean.value, client_data);
499         (*pr)(" ", client_data);
500
501         cql_transform_r(ct, cn->u.boolean.left, pr, client_data,
502                         prefix_ar, prefix_level);
503         cql_transform_r(ct, cn->u.boolean.right, pr, client_data,
504                         prefix_ar, prefix_level);
505     }
506 }
507
508 int cql_transform(cql_transform_t ct,
509                   struct cql_node *cn,
510                   void (*pr)(const char *buf, void *client_data),
511                   void *client_data)
512 {
513     struct cql_node *prefix_ar[20], **pp;
514     struct cql_prop_entry *e;
515
516     ct->error = 0;
517     if (ct->addinfo)
518         free (ct->addinfo);
519     ct->addinfo = 0;
520
521     prefix_ar[0] = 0;
522     pp = &prefix_ar[0];
523     for (e = ct->entry; e ; e = e->next)
524     {
525         if (!memcmp(e->pattern, "set.", 4))
526         {
527             *pp = cql_node_mk_mod(e->pattern+4, e->value);
528             pp = &(*pp)->u.mod.next;
529         }
530         else if (!strcmp(e->pattern, "set"))
531         {
532             *pp = cql_node_mk_mod(0, e->value);
533             pp = &(*pp)->u.mod.next;
534         }
535     }
536     cql_transform_r (ct, cn, pr, client_data, prefix_ar, 1);
537     cql_node_destroy(prefix_ar[0]);
538     return ct->error;
539 }
540
541
542 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
543 {
544     return cql_transform(ct, cn, cql_fputs, f);
545 }
546
547 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
548                       char *out, int max)
549 {
550     struct cql_buf_write_info info;
551     int r;
552
553     info.off = 0;
554     info.max = max;
555     info.buf = out;
556     r = cql_transform(ct, cn, cql_buf_write_handler, &info);
557     if (info.off >= 0)
558         info.buf[info.off] = '\0';
559     return r;
560 }
561
562 int cql_transform_error(cql_transform_t ct, const char **addinfo)
563 {
564     *addinfo = ct->addinfo;
565     return ct->error;
566 }