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