Extend MARC-8 to handle ESC-G0 EACC. Fix conversion order for MARCXML
[yaz-moved-to-github.git] / src / cqltransform.c
1 /* $Id: cqltransform.c,v 1.8 2004-03-15 21:39:06 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 char *wcchar(const char *term, int length)
212 {
213     char *best = 0;
214     char *current;
215     char *whichp;
216
217     for (whichp = "*?"; *whichp != '\0'; whichp++) {
218         current = 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 = 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.  If there's no "truncation.none"
316              * configuration element, that's an error which we
317              * indicate (rather tangentially) as 30 "Too many masking
318              * characters in term".  28 would be equally meaningful
319              * (or meaningless) but using a different value allows us
320              * to differentiate between this case and the previous
321              * one.
322              */
323             cql_pr_attr(ct, "truncation", "none", 0,
324                         pr, client_data, 30);
325         }
326     }
327
328     (*pr)("\"", client_data);
329     for (i = 0; i<length; i++)
330     {
331         char buf[2];
332         buf[0] = term[i];
333         buf[1] = 0;
334         (*pr)(buf, client_data);
335     }
336     (*pr)("\" ", client_data);
337 }
338
339 void emit_wordlist(cql_transform_t ct,
340                    struct cql_node *cn,
341                    void (*pr)(const char *buf, void *client_data),
342                    void *client_data,
343                    const char *op)
344 {
345     const char *cp0 = cn->u.st.term;
346     const char *cp1;
347     const char *last_term = 0;
348     int last_length = 0;
349     while(cp0)
350     {
351         while (*cp0 == ' ')
352             cp0++;
353         cp1 = strchr(cp0, ' ');
354         if (last_term)
355         {
356             (*pr)("@", client_data);
357             (*pr)(op, client_data);
358             (*pr)(" ", client_data);
359             emit_term(ct, last_term, last_length, pr, client_data);
360         }
361         last_term = cp0;
362         if (cp1)
363             last_length = cp1 - cp0;
364         else
365             last_length = strlen(cp0);
366         cp0 = cp1;
367     }
368     if (last_term)
369         emit_term(ct, last_term, last_length, pr, client_data);
370 }
371
372 void cql_transform_r(cql_transform_t ct,
373                      struct cql_node *cn,
374                      void (*pr)(const char *buf, void *client_data),
375                      void *client_data)
376 {
377     const char *ns;
378
379     if (!cn)
380         return;
381     switch (cn->which)
382     {
383     case CQL_NODE_ST:
384         ns = cn->u.st.index_uri;
385         if (ns)
386         {
387             if (!strcmp(ns, cql_uri())
388                 && cn->u.st.index && !strcmp(cn->u.st.index, "resultSet"))
389             {
390                 (*pr)("@set \"", client_data);
391                 (*pr)(cn->u.st.term, client_data);
392                 (*pr)("\" ", client_data);
393                 return ;
394             }
395             cql_pr_attr_uri(ct, "index", ns,
396                             cn->u.st.index, "serverChoice",
397                             pr, client_data, 16);
398         }
399         else
400         {
401             if (!ct->error)
402             {
403                 ct->error = 15;
404                 ct->addinfo = 0;
405             }
406         }
407         if (cn->u.st.relation && !strcmp(cn->u.st.relation, "="))
408             cql_pr_attr(ct, "relation", "eq", "scr",
409                         pr, client_data, 19);
410         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, "<="))
411             cql_pr_attr(ct, "relation", "le", "scr",
412                         pr, client_data, 19);
413         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, ">="))
414             cql_pr_attr(ct, "relation", "ge", "scr",
415                         pr, client_data, 19);
416         else
417             cql_pr_attr(ct, "relation", cn->u.st.relation, "eq",
418                         pr, client_data, 19);
419         if (cn->u.st.modifiers)
420         {
421             struct cql_node *mod = cn->u.st.modifiers;
422             for (; mod; mod = mod->u.st.modifiers)
423             {
424                 cql_pr_attr(ct, "relationModifier", mod->u.st.term, 0,
425                             pr, client_data, 20);
426             }
427         }
428         cql_pr_attr(ct, "structure", cn->u.st.relation, 0,
429                     pr, client_data, 24);
430         if (cn->u.st.relation && !strcmp(cn->u.st.relation, "all"))
431         {
432             emit_wordlist(ct, cn, pr, client_data, "and");
433         }
434         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, "any"))
435         {
436             emit_wordlist(ct, cn, pr, client_data, "or");
437         }
438         else
439         {
440             emit_term(ct, cn->u.st.term, strlen(cn->u.st.term),
441                       pr, client_data);
442         }
443         break;
444     case CQL_NODE_BOOL:
445         (*pr)("@", client_data);
446         (*pr)(cn->u.boolean.value, client_data);
447         (*pr)(" ", client_data);
448
449         cql_transform_r(ct, cn->u.boolean.left, pr, client_data);
450         cql_transform_r(ct, cn->u.boolean.right, pr, client_data);
451     }
452 }
453
454 int cql_transform(cql_transform_t ct,
455                   struct cql_node *cn,
456                   void (*pr)(const char *buf, void *client_data),
457                   void *client_data)
458 {
459     struct cql_prop_entry *e;
460
461     ct->error = 0;
462     if (ct->addinfo)
463         xfree (ct->addinfo);
464     ct->addinfo = 0;
465
466     for (e = ct->entry; e ; e = e->next)
467     {
468         if (!memcmp(e->pattern, "set.", 4))
469             cql_apply_prefix(cn, e->pattern+4, e->value);
470         else if (!strcmp(e->pattern, "set"))
471             cql_apply_prefix(cn, 0, e->value);
472     }
473     cql_transform_r (ct, cn, pr, client_data);
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 }