5a4025ee39917b373c13e0625f7233a30422bd5a
[yaz-moved-to-github.git] / src / cqltransform.c
1 /* $Id: cqltransform.c,v 1.5 2003-12-18 17:00:55 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 void emit_term(cql_transform_t ct,
181                const char *term, int length,
182                void (*pr)(const char *buf, void *client_data),
183                void *client_data)
184 {
185     int i;
186     if (length > 0)
187     {
188         if (length > 1 && term[0] == '^' && term[length-1] == '^')
189         {
190             cql_pr_attr(ct, "position.", "firstAndLast", 0,
191                         pr, client_data, 32);
192             term++;
193             length -= 2;
194         }
195         else if (term[0] == '^')
196         {
197             cql_pr_attr(ct, "position.", "first", 0,
198                         pr, client_data, 32);
199             term++;
200         }
201         else if (term[length-1] == '^')
202         {
203             cql_pr_attr(ct, "position.", "last", 0,
204                         pr, client_data, 32);
205             length--;
206         }
207         else
208         {
209             cql_pr_attr(ct, "position.", "any", 0,
210                         pr, client_data, 32);
211         }
212     }
213     (*pr)("\"", client_data);
214     for (i = 0; i<length; i++)
215     {
216         char buf[2];
217         buf[0] = term[i];
218         buf[1] = 0;
219         (*pr)(buf, client_data);
220     }
221     (*pr)("\" ", client_data);
222 }
223
224 void emit_wordlist(cql_transform_t ct,
225                    struct cql_node *cn,
226                    void (*pr)(const char *buf, void *client_data),
227                    void *client_data,
228                    const char *op)
229 {
230     const char *cp0 = cn->u.st.term;
231     const char *cp1;
232     const char *last_term = 0;
233     int last_length = 0;
234     while(cp0)
235     {
236         while (*cp0 == ' ')
237             cp0++;
238         cp1 = strchr(cp0, ' ');
239         if (last_term)
240         {
241             (*pr)("@", client_data);
242             (*pr)(op, client_data);
243             (*pr)(" ", client_data);
244             emit_term(ct, last_term, last_length, pr, client_data);
245         }
246         last_term = cp0;
247         if (cp1)
248             last_length = cp1 - cp0;
249         else
250             last_length = strlen(cp0);
251         cp0 = cp1;
252     }
253     if (last_term)
254         emit_term(ct, last_term, last_length, pr, client_data);
255 }
256
257
258 static const char *cql_get_ns(cql_transform_t ct,
259                               struct cql_node *cn,
260                               struct cql_node **prefix_ar, int prefix_level,
261                               const char **n_prefix,
262                               const char **n_suffix)
263 {
264     int i;
265     const char *ns = 0;
266     char prefix[32];
267     const char *cp = cn->u.st.index;
268     const char *cp_dot = strchr(cp, '.');
269
270     /* strz current prefix (empty if not given) */
271     if (cp_dot && cp_dot-cp < sizeof(prefix))
272     {
273         memcpy (prefix, cp, cp_dot - cp);
274         prefix[cp_dot - cp] = 0;
275     }
276     else
277         *prefix = 0;
278
279     /* 2. lookup in prefix_ar. and return NS */
280     for (i = prefix_level; !ns && --i >= 0; )
281     {
282         struct cql_node *cn_prefix = prefix_ar[i];
283         for (; cn_prefix; cn_prefix = cn_prefix->u.mod.next)
284         {
285             if (*prefix && cn_prefix->u.mod.name &&
286                 !strcmp(prefix, cn_prefix->u.mod.name))
287             {
288                 ns = cn_prefix->u.mod.value;
289                 break;
290             }
291             else if (!*prefix && !cn_prefix->u.mod.name)
292             {
293                 ns = cn_prefix->u.mod.value;
294                 break;
295             }
296         }
297     }
298     if (!ns)
299     {
300         if (!ct->error)
301         {
302             ct->error = 15;
303             ct->addinfo = strdup(prefix);
304         }
305         return 0;
306     }
307     /* 3. lookup in set.NS for new prefix */
308     *n_prefix = cql_lookup_value(ct, "set.", ns);
309     if (!*n_prefix)
310     {
311         if (!ct->error)
312         {
313             ct->error = 15;
314             ct->addinfo = strdup(ns);
315         }
316         return 0;
317     }
318     /* 4. lookup index.prefix. */
319     
320     cp = cn->u.st.index;
321     cp_dot = strchr(cp, '.');
322     
323     *n_suffix = cp_dot ? cp_dot+1 : cp;
324     return ns;
325 }
326
327 void cql_transform_r(cql_transform_t ct,
328                      struct cql_node *cn,
329                      void (*pr)(const char *buf, void *client_data),
330                      void *client_data,
331                      struct cql_node **prefix_ar, int prefix_level)
332 {
333     const char *ns, *n_prefix, *n_suffix;
334
335     if (!cn)
336         return;
337     switch (cn->which)
338     {
339     case CQL_NODE_ST:
340         if (cn->u.st.prefixes && prefix_level < 20)
341             prefix_ar[prefix_level++] = cn->u.st.prefixes;
342         ns = cql_get_ns(ct, cn, prefix_ar, prefix_level, &n_prefix, &n_suffix);
343         if (ns)
344         {
345             char n_full[64];
346             sprintf (n_full, "%.20s.%.40s", n_prefix, n_suffix);
347         
348             if ((!strcmp(ns, "http://www.loc.gov/zing/cql/context-sets/cql/v1.1/") ||
349                  !strcmp(ns, "http://www.loc.gov/zing/cql/srw-indexes/v1.0/"))
350                 && !strcmp(n_suffix, "resultSet"))
351             {
352                 (*pr)("@set \"", client_data);
353                 (*pr)(cn->u.st.term, client_data);
354                 (*pr)("\" ", client_data);
355                 return ;
356             }
357             /* ### It would be nice if this could fall back to whichever 
358                of cql.serverChoice and srw.serverChoice is defined */
359             if (!cql_pr_attr(ct, "index.", n_full, "cql.serverChoice",
360                              pr, client_data, 16)) {
361                 /* No index.foo; reset error and fall back to qualifier.foo */
362                 if (ct->error == 16) ct->error = 0;
363                 cql_pr_attr(ct, "qualifier.", n_full, "cql.serverChoice",
364                             pr, client_data, 16);
365             }
366         }
367
368         if (cn->u.st.relation && !strcmp(cn->u.st.relation, "="))
369             cql_pr_attr(ct, "relation.", "eq", "scr",
370                         pr, client_data, 19);
371         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, "<="))
372             cql_pr_attr(ct, "relation.", "le", "scr",
373                         pr, client_data, 19);
374         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, ">="))
375             cql_pr_attr(ct, "relation.", "ge", "scr",
376                         pr, client_data, 19);
377         else
378             cql_pr_attr(ct, "relation.", cn->u.st.relation, "eq",
379                         pr, client_data, 19);
380         if (cn->u.st.modifiers)
381         {
382             struct cql_node *mod = cn->u.st.modifiers;
383             for (; mod; mod = mod->u.mod.next)
384             {
385                 cql_pr_attr(ct, "relationModifier.", mod->u.mod.value, 0,
386                             pr, client_data, 20);
387             }
388         }
389         cql_pr_attr(ct, "structure.", cn->u.st.relation, 0,
390                     pr, client_data, 24);
391         if (cn->u.st.relation && !strcmp(cn->u.st.relation, "all"))
392         {
393             emit_wordlist(ct, cn, pr, client_data, "and");
394         }
395         else if (cn->u.st.relation && !strcmp(cn->u.st.relation, "any"))
396         {
397             emit_wordlist(ct, cn, pr, client_data, "or");
398         }
399         else
400         {
401             emit_term(ct, cn->u.st.term, strlen(cn->u.st.term),
402                       pr, client_data);
403         }
404         break;
405     case CQL_NODE_BOOL:
406         if (cn->u.boolean.prefixes && prefix_level < 20)
407             prefix_ar[prefix_level++] = cn->u.boolean.prefixes;
408         (*pr)("@", client_data);
409         (*pr)(cn->u.boolean.value, client_data);
410         (*pr)(" ", client_data);
411
412         cql_transform_r(ct, cn->u.boolean.left, pr, client_data,
413                         prefix_ar, prefix_level);
414         cql_transform_r(ct, cn->u.boolean.right, pr, client_data,
415                         prefix_ar, prefix_level);
416     }
417 }
418
419 int cql_transform(cql_transform_t ct,
420                   struct cql_node *cn,
421                   void (*pr)(const char *buf, void *client_data),
422                   void *client_data)
423 {
424     struct cql_node *prefix_ar[20], **pp;
425     struct cql_prop_entry *e;
426
427     ct->error = 0;
428     if (ct->addinfo)
429         free (ct->addinfo);
430     ct->addinfo = 0;
431
432     prefix_ar[0] = 0;
433     pp = &prefix_ar[0];
434     for (e = ct->entry; e ; e = e->next)
435     {
436         if (!memcmp(e->pattern, "set.", 4))
437         {
438             *pp = cql_node_mk_mod(e->pattern+4, e->value);
439             pp = &(*pp)->u.mod.next;
440         }
441         else if (!strcmp(e->pattern, "set"))
442         {
443             *pp = cql_node_mk_mod(0, e->value);
444             pp = &(*pp)->u.mod.next;
445         }
446     }
447     cql_transform_r (ct, cn, pr, client_data, prefix_ar, 1);
448     cql_node_destroy(prefix_ar[0]);
449     return ct->error;
450 }
451
452
453 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
454 {
455     return cql_transform(ct, cn, cql_fputs, f);
456 }
457
458 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
459                       char *out, int max)
460 {
461     struct cql_buf_write_info info;
462     int r;
463
464     info.off = 0;
465     info.max = max;
466     info.buf = out;
467     r = cql_transform(ct, cn, cql_buf_write_handler, &info);
468     if (info.off >= 0)
469         info.buf[info.off] = '\0';
470     return r;
471 }
472
473 int cql_transform_error(cql_transform_t ct, const char **addinfo)
474 {
475     *addinfo = ct->addinfo;
476     return ct->error;
477 }