Expanded tabs in all source files. Added vim/emacs local variables
[yaz-moved-to-github.git] / src / cqltransform.c
1 /* $Id: cqltransform.c,v 1.15 2005-06-25 15:46:03 adam 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 (!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 /* Returns location of first wildcard character in the `length'
213  * characters starting at `term', or a null pointer of there are
214  * none -- like memchr().
215  */
216 static const char *wcchar(const char *term, int length)
217 {
218     const char *best = 0;
219     const char *current;
220     char *whichp;
221
222     for (whichp = "*?"; *whichp != '\0'; whichp++) {
223         current = (const char *) memchr(term, *whichp, length);
224         if (current != 0 && (best == 0 || current < best))
225             best = current;
226     }
227
228     return best;
229 }
230
231
232 void emit_term(cql_transform_t ct,
233                const char *term, int length,
234                void (*pr)(const char *buf, void *client_data),
235                void *client_data)
236 {
237     int i;
238     if (length > 0)
239     {
240         if (length > 1 && term[0] == '^' && term[length-1] == '^')
241         {
242             cql_pr_attr(ct, "position", "firstAndLast", 0,
243                         pr, client_data, 32);
244             term++;
245             length -= 2;
246         }
247         else if (term[0] == '^')
248         {
249             cql_pr_attr(ct, "position", "first", 0,
250                         pr, client_data, 32);
251             term++;
252             length--;
253         }
254         else if (term[length-1] == '^')
255         {
256             cql_pr_attr(ct, "position", "last", 0,
257                         pr, client_data, 32);
258             length--;
259         }
260         else
261         {
262             cql_pr_attr(ct, "position", "any", 0,
263                         pr, client_data, 32);
264         }
265     }
266
267     if (length > 0)
268     {
269         /* Check for well-known globbing patterns that represent
270          * simple truncation attributes as expected by, for example,
271          * Bath-compliant server.  If we find such a pattern but
272          * there's no mapping for it, that's fine: we just use a
273          * general pattern-matching attribute.
274          */
275         if (length > 1 && term[0] == '*' && term[length-1] == '*' &&
276             wcchar(term+1, length-2) == 0 &&
277             cql_pr_attr(ct, "truncation", "both", 0,
278                         pr, client_data, 0)) {
279             term++;
280             length -= 2;
281         }
282         else if (term[0] == '*' &&
283                  wcchar(term+1, length-1) == 0 &&
284                  cql_pr_attr(ct, "truncation", "left", 0,
285                              pr, client_data, 0)) {
286             term++;
287             length--;
288         }
289         else if (term[length-1] == '*' &&
290                  wcchar(term, length-1) == 0 &&
291                  cql_pr_attr(ct, "truncation", "right", 0,
292                              pr, client_data, 0)) {
293             length--;
294         }
295         else if (wcchar(term, length))
296         {
297             /* We have one or more wildcard characters, but not in a
298              * way that can be dealt with using only the standard
299              * left-, right- and both-truncation attributes.  We need
300              * to translate the pattern into a Z39.58-type pattern,
301              * which has been supported in BIB-1 since 1996.  If
302              * there's no configuration element for "truncation.z3958"
303              * we indicate this as error 28 "Masking character not
304              * supported".
305              */
306             int i;
307             char *mem;
308             cql_pr_attr(ct, "truncation", "z3958", 0,
309                         pr, client_data, 28);
310             mem = (char *) xmalloc(length+1);
311             for (i = 0; i < length; i++) {
312                 if (term[i] == '*')      mem[i] = '?';
313                 else if (term[i] == '?') mem[i] = '#';
314                 else                     mem[i] = term[i];
315             }
316             mem[length] = '\0';
317             term = mem;
318         }
319         else {
320             /* No masking characters.  Use "truncation.none" if given. */
321             cql_pr_attr(ct, "truncation", "none", 0,
322                         pr, client_data, 0);
323         }
324     }
325
326     (*pr)("\"", client_data);
327     for (i = 0; i<length; i++)
328     {
329         char buf[2];
330         buf[0] = term[i];
331         buf[1] = 0;
332         (*pr)(buf, client_data);
333     }
334     (*pr)("\" ", client_data);
335 }
336
337 void emit_wordlist(cql_transform_t ct,
338                    struct cql_node *cn,
339                    void (*pr)(const char *buf, void *client_data),
340                    void *client_data,
341                    const char *op)
342 {
343     const char *cp0 = cn->u.st.term;
344     const char *cp1;
345     const char *last_term = 0;
346     int last_length = 0;
347     while(cp0)
348     {
349         while (*cp0 == ' ')
350             cp0++;
351         cp1 = strchr(cp0, ' ');
352         if (last_term)
353         {
354             (*pr)("@", client_data);
355             (*pr)(op, client_data);
356             (*pr)(" ", client_data);
357             emit_term(ct, last_term, last_length, pr, client_data);
358         }
359         last_term = cp0;
360         if (cp1)
361             last_length = cp1 - cp0;
362         else
363             last_length = strlen(cp0);
364         cp0 = cp1;
365     }
366     if (last_term)
367         emit_term(ct, last_term, last_length, pr, client_data);
368 }
369
370 void cql_transform_r(cql_transform_t ct,
371                      struct cql_node *cn,
372                      void (*pr)(const char *buf, void *client_data),
373                      void *client_data)
374 {
375     const char *ns;
376
377     if (!cn)
378         return;
379     switch (cn->which)
380     {
381     case CQL_NODE_ST:
382         ns = cn->u.st.index_uri;
383         if (ns)
384         {
385             if (!strcmp(ns, cql_uri())
386                 && cn->u.st.index && !strcmp(cn->u.st.index, "resultSet"))
387             {
388                 (*pr)("@set \"", client_data);
389                 (*pr)(cn->u.st.term, client_data);
390                 (*pr)("\" ", client_data);
391                 return ;
392             }
393             cql_pr_attr_uri(ct, "index", ns,
394                             cn->u.st.index, "serverChoice",
395                             pr, client_data, 16);
396         }
397         else
398         {
399             if (!ct->error)
400             {
401                 ct->error = 15;
402                 ct->addinfo = 0;
403             }
404         }
405         if (cn->u.st.relation && !strcmp(cn->u.st.relation, "="))
406             cql_pr_attr(ct, "relation", "eq", "scr",
407                         pr, client_data, 19);
408         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, "<="))
409             cql_pr_attr(ct, "relation", "le", "scr",
410                         pr, client_data, 19);
411         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, ">="))
412             cql_pr_attr(ct, "relation", "ge", "scr",
413                         pr, client_data, 19);
414         else
415             cql_pr_attr(ct, "relation", cn->u.st.relation, "eq",
416                         pr, client_data, 19);
417         if (cn->u.st.modifiers)
418         {
419             struct cql_node *mod = cn->u.st.modifiers;
420             for (; mod; mod = mod->u.st.modifiers)
421             {
422                 cql_pr_attr(ct, "relationModifier", mod->u.st.index, 0,
423                             pr, client_data, 20);
424             }
425         }
426         cql_pr_attr(ct, "structure", cn->u.st.relation, 0,
427                     pr, client_data, 24);
428         if (cn->u.st.relation && !strcmp(cn->u.st.relation, "all"))
429         {
430             emit_wordlist(ct, cn, pr, client_data, "and");
431         }
432         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, "any"))
433         {
434             emit_wordlist(ct, cn, pr, client_data, "or");
435         }
436         else
437         {
438             emit_term(ct, cn->u.st.term, strlen(cn->u.st.term),
439                       pr, client_data);
440         }
441         break;
442     case CQL_NODE_BOOL:
443         (*pr)("@", client_data);
444         (*pr)(cn->u.boolean.value, client_data);
445         (*pr)(" ", client_data);
446
447         cql_transform_r(ct, cn->u.boolean.left, pr, client_data);
448         cql_transform_r(ct, cn->u.boolean.right, pr, client_data);
449     }
450 }
451
452 int cql_transform(cql_transform_t ct,
453                   struct cql_node *cn,
454                   void (*pr)(const char *buf, void *client_data),
455                   void *client_data)
456 {
457     struct cql_prop_entry *e;
458     NMEM nmem = nmem_create();
459
460     ct->error = 0;
461     if (ct->addinfo)
462         xfree (ct->addinfo);
463     ct->addinfo = 0;
464
465     for (e = ct->entry; e ; e = e->next)
466     {
467         if (!memcmp(e->pattern, "set.", 4))
468             cql_apply_prefix(nmem, cn, e->pattern+4, e->value);
469         else if (!strcmp(e->pattern, "set"))
470             cql_apply_prefix(nmem, cn, 0, e->value);
471     }
472     cql_transform_r (ct, cn, pr, client_data);
473     nmem_destroy(nmem);
474     return ct->error;
475 }
476
477
478 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
479 {
480     return cql_transform(ct, cn, cql_fputs, f);
481 }
482
483 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
484                       char *out, int max)
485 {
486     struct cql_buf_write_info info;
487     int r;
488
489     info.off = 0;
490     info.max = max;
491     info.buf = out;
492     r = cql_transform(ct, cn, cql_buf_write_handler, &info);
493     if (info.off >= 0)
494         info.buf[info.off] = '\0';
495     return r;
496 }
497
498 int cql_transform_error(cql_transform_t ct, const char **addinfo)
499 {
500     *addinfo = ct->addinfo;
501     return ct->error;
502 }
503 /*
504  * Local variables:
505  * c-basic-offset: 4
506  * indent-tabs-mode: nil
507  * End:
508  * vim: shiftwidth=4 tabstop=8 expandtab
509  */
510