Make object member to temporary member (WRBUF)
[yaz-moved-to-github.git] / src / cqltransform.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) Index Data
3  * See the file LICENSE for details.
4  */
5 /**
6  * \file cqltransform.c
7  * \brief Implements CQL transform (CQL to RPN conversion).
8  *
9  * Evaluation order of rules:
10  *
11  * always
12  * relation
13  * structure
14  * position
15  * truncation
16  * index
17  * relationModifier
18  */
19 #if HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 #include <assert.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <yaz/rpn2cql.h>
27 #include <yaz/xmalloc.h>
28 #include <yaz/diagsrw.h>
29 #include <yaz/tokenizer.h>
30 #include <yaz/wrbuf.h>
31 #include <yaz/z-core.h>
32 #include <yaz/matchstr.h>
33 #include <yaz/oid_db.h>
34 #include <yaz/log.h>
35
36 struct cql_prop_entry {
37     char *pattern;
38     char *value;
39     Z_AttributeList attr_list;
40     struct cql_prop_entry *next;
41 };
42
43 struct cql_transform_t_ {
44     struct cql_prop_entry *entry;
45     yaz_tok_cfg_t tok_cfg;
46     int error;
47     char *addinfo;
48     NMEM nmem;
49 };
50
51
52 cql_transform_t cql_transform_create(void)
53 {
54     cql_transform_t ct = (cql_transform_t) xmalloc(sizeof(*ct));
55     ct->tok_cfg = yaz_tok_cfg_create();
56     ct->error = 0;
57     ct->addinfo = 0;
58     ct->entry = 0;
59     ct->nmem = nmem_create();
60     return ct;
61 }
62
63 static int cql_transform_parse_tok_line(cql_transform_t ct,
64                                         const char *pattern,
65                                         yaz_tok_parse_t tp)
66 {
67     int ae_num = 0;
68     Z_AttributeElement *ae[20];
69     int ret = 0; /* 0=OK, != 0 FAIL */
70     int t;
71     WRBUF w = wrbuf_alloc();
72
73     t = yaz_tok_move(tp);
74
75     while (t == YAZ_TOK_STRING && ae_num < 20)
76     {
77         WRBUF type_str = wrbuf_alloc();
78         WRBUF set_str = 0;
79         Z_AttributeElement *elem = 0;
80         const char *value_str = 0;
81         /* attset type=value  OR  type=value */
82
83         elem = (Z_AttributeElement *) nmem_malloc(ct->nmem, sizeof(*elem));
84         elem->attributeSet = 0;
85         ae[ae_num] = elem;
86         wrbuf_puts(w, yaz_tok_parse_string(tp));
87         wrbuf_puts(type_str, yaz_tok_parse_string(tp));
88         t = yaz_tok_move(tp);
89         if (t == YAZ_TOK_EOF)
90         {
91             wrbuf_destroy(type_str);
92             if (set_str)
93                 wrbuf_destroy(set_str);
94             break;
95         }
96         if (t == YAZ_TOK_STRING)
97         {
98             wrbuf_puts(w, " ");
99             wrbuf_puts(w, yaz_tok_parse_string(tp));
100             set_str = type_str;
101
102             elem->attributeSet =
103                 yaz_string_to_oid_nmem(yaz_oid_std(), CLASS_ATTSET,
104                                        wrbuf_cstr(set_str), ct->nmem);
105
106             type_str = wrbuf_alloc();
107             wrbuf_puts(type_str, yaz_tok_parse_string(tp));
108             t = yaz_tok_move(tp);
109         }
110         elem->attributeType = nmem_intdup(ct->nmem, 0);
111         if (sscanf(wrbuf_cstr(type_str), ODR_INT_PRINTF, elem->attributeType)
112             != 1)
113         {
114             wrbuf_destroy(type_str);
115             if (set_str)
116                 wrbuf_destroy(set_str);
117             yaz_log(YLOG_WARN, "Expected numeric attribute type");
118             ret = -1;
119             break;
120         }
121
122         wrbuf_destroy(type_str);
123         if (set_str)
124             wrbuf_destroy(set_str);
125
126         if (t != '=')
127         {
128             yaz_log(YLOG_WARN, "Expected = after after attribute type");
129             ret = -1;
130             break;
131         }
132         t = yaz_tok_move(tp);
133         if (t != YAZ_TOK_STRING) /* value */
134         {
135             yaz_log(YLOG_WARN, "Missing attribute value");
136             ret = -1;
137             break;
138         }
139         value_str = yaz_tok_parse_string(tp);
140         if (yaz_isdigit(*value_str))
141         {
142             elem->which = Z_AttributeValue_numeric;
143             elem->value.numeric =
144                 nmem_intdup(ct->nmem, atoi(value_str));
145         }
146         else
147         {
148             Z_ComplexAttribute *ca = (Z_ComplexAttribute *)
149                 nmem_malloc(ct->nmem, sizeof(*ca));
150             elem->which = Z_AttributeValue_complex;
151             elem->value.complex = ca;
152             ca->num_list = 1;
153             ca->list = (Z_StringOrNumeric **)
154                 nmem_malloc(ct->nmem, sizeof(Z_StringOrNumeric *));
155             ca->list[0] = (Z_StringOrNumeric *)
156                 nmem_malloc(ct->nmem, sizeof(Z_StringOrNumeric));
157             ca->list[0]->which = Z_StringOrNumeric_string;
158             ca->list[0]->u.string = nmem_strdup(ct->nmem, value_str);
159             ca->num_semanticAction = 0;
160             ca->semanticAction = 0;
161         }
162         wrbuf_puts(w, "=");
163         wrbuf_puts(w, yaz_tok_parse_string(tp));
164         t = yaz_tok_move(tp);
165         wrbuf_puts(w, " ");
166         ae_num++;
167     }
168     if (ret == 0) /* OK? */
169     {
170         struct cql_prop_entry **pp = &ct->entry;
171         while (*pp)
172             pp = &(*pp)->next;
173         *pp = (struct cql_prop_entry *) xmalloc(sizeof(**pp));
174         (*pp)->pattern = xstrdup(pattern);
175         (*pp)->value = xstrdup(wrbuf_cstr(w));
176
177         (*pp)->attr_list.num_attributes = ae_num;
178         if (ae_num == 0)
179             (*pp)->attr_list.attributes = 0;
180         else
181         {
182             (*pp)->attr_list.attributes = (Z_AttributeElement **)
183                 nmem_malloc(ct->nmem,
184                             ae_num * sizeof(Z_AttributeElement *));
185             memcpy((*pp)->attr_list.attributes, ae,
186                    ae_num * sizeof(Z_AttributeElement *));
187         }
188         (*pp)->next = 0;
189
190         if (0)
191         {
192             ODR pr = odr_createmem(ODR_PRINT);
193             Z_AttributeList *alp = &(*pp)->attr_list;
194             odr_setprint(pr, yaz_log_file());
195             z_AttributeList(pr, &alp, 0, 0);
196             odr_setprint(pr, 0);
197             odr_destroy(pr);
198         }
199     }
200     wrbuf_destroy(w);
201     return ret;
202 }
203
204 int cql_transform_define_pattern(cql_transform_t ct, const char *pattern,
205                                  const char *value)
206 {
207     int r;
208     yaz_tok_parse_t tp = yaz_tok_parse_buf(ct->tok_cfg, value);
209     yaz_tok_cfg_single_tokens(ct->tok_cfg, "=");
210     r = cql_transform_parse_tok_line(ct, pattern, tp);
211     yaz_tok_parse_destroy(tp);
212     return r;
213 }
214
215 cql_transform_t cql_transform_open_FILE(FILE *f)
216 {
217     cql_transform_t ct = cql_transform_create();
218     char line[1024];
219
220     yaz_tok_cfg_single_tokens(ct->tok_cfg, "=");
221
222     while (fgets(line, sizeof(line)-1, f))
223     {
224         yaz_tok_parse_t tp = yaz_tok_parse_buf(ct->tok_cfg, line);
225         int t;
226         t = yaz_tok_move(tp);
227         if (t == YAZ_TOK_STRING)
228         {
229             char * pattern = xstrdup(yaz_tok_parse_string(tp));
230             t = yaz_tok_move(tp);
231             if (t != '=')
232             {
233                 yaz_tok_parse_destroy(tp);
234                 cql_transform_close(ct);
235                 return 0;
236             }
237             if (cql_transform_parse_tok_line(ct, pattern, tp))
238             {
239                 yaz_tok_parse_destroy(tp);
240                 cql_transform_close(ct);
241                 return 0;
242             }
243             xfree(pattern);
244         }
245         else if (t != YAZ_TOK_EOF)
246         {
247             yaz_tok_parse_destroy(tp);
248             cql_transform_close(ct);
249             return 0;
250         }
251         yaz_tok_parse_destroy(tp);
252     }
253     return ct;
254 }
255
256 void cql_transform_close(cql_transform_t ct)
257 {
258     struct cql_prop_entry *pe;
259     if (!ct)
260         return;
261     pe = ct->entry;
262     while (pe)
263     {
264         struct cql_prop_entry *pe_next = pe->next;
265         xfree(pe->pattern);
266         xfree(pe->value);
267         xfree(pe);
268         pe = pe_next;
269     }
270     xfree(ct->addinfo);
271     yaz_tok_cfg_destroy(ct->tok_cfg);
272     nmem_destroy(ct->nmem);
273     xfree(ct);
274 }
275
276 cql_transform_t cql_transform_open_fname(const char *fname)
277 {
278     cql_transform_t ct;
279     FILE *f = fopen(fname, "r");
280     if (!f)
281         return 0;
282     ct = cql_transform_open_FILE(f);
283     fclose(f);
284     return ct;
285 }
286
287 #if 0
288 struct Z_AttributeElement {
289         Z_AttributeSetId *attributeSet; /* OPT */
290         int *attributeType;
291         int which;
292         union {
293                 int *numeric;
294                 Z_ComplexAttribute *complex;
295 #define Z_AttributeValue_numeric 1
296 #define Z_AttributeValue_complex 2
297         } value;
298 };
299 #endif
300
301 static int compare_attr(Z_AttributeElement *a, Z_AttributeElement *b)
302 {
303     ODR odr_a = odr_createmem(ODR_ENCODE);
304     ODR odr_b = odr_createmem(ODR_ENCODE);
305     int len_a, len_b;
306     char *buf_a, *buf_b;
307     int ret;
308
309     z_AttributeElement(odr_a, &a, 0, 0);
310     z_AttributeElement(odr_b, &b, 0, 0);
311
312     buf_a = odr_getbuf(odr_a, &len_a, 0);
313     buf_b = odr_getbuf(odr_b, &len_b, 0);
314
315     ret = yaz_memcmp(buf_a, buf_b, len_a, len_b);
316
317     odr_destroy(odr_a);
318     odr_destroy(odr_b);
319     return ret;
320 }
321
322 const char *cql_lookup_reverse(cql_transform_t ct,
323                                const char *category,
324                                Z_AttributeList *attributes)
325 {
326     struct cql_prop_entry *e;
327     size_t clen = strlen(category);
328     for (e = ct->entry; e; e = e->next)
329     {
330         if (!strncmp(e->pattern, category, clen))
331         {
332             /* category matches.. See if attributes in pattern value
333                are all listed in actual attributes */
334             int i;
335             for (i = 0; i < e->attr_list.num_attributes; i++)
336             {
337                 /* entry attribute */
338                 Z_AttributeElement *e_ae = e->attr_list.attributes[i];
339                 int j;
340                 for (j = 0; j < attributes->num_attributes; j++)
341                 {
342                     /* actual attribute */
343                     Z_AttributeElement *a_ae = attributes->attributes[j];
344                     int r = compare_attr(e_ae, a_ae);
345                     if (r == 0)
346                         break;
347                 }
348                 if (j == attributes->num_attributes)
349                     break; /* i was not found at all.. try next pattern */
350
351             }
352             if (i == e->attr_list.num_attributes)
353                 return e->pattern + clen;
354         }
355     }
356     return 0;
357 }
358
359 static const char *cql_lookup_property(cql_transform_t ct,
360                                        const char *pat1, const char *pat2,
361                                        const char *pat3)
362 {
363     char pattern[120];
364     struct cql_prop_entry *e;
365
366     if (pat1 && pat2 && pat3)
367         sprintf(pattern, "%.39s.%.39s.%.39s", pat1, pat2, pat3);
368     else if (pat1 && pat2)
369         sprintf(pattern, "%.39s.%.39s", pat1, pat2);
370     else if (pat1 && pat3)
371         sprintf(pattern, "%.39s.%.39s", pat1, pat3);
372     else if (pat1)
373         sprintf(pattern, "%.39s", pat1);
374     else
375         return 0;
376
377     for (e = ct->entry; e; e = e->next)
378     {
379         if (!cql_strcmp(e->pattern, pattern))
380             return e->value;
381     }
382     return 0;
383 }
384
385 int cql_pr_attr_uri(cql_transform_t ct, const char *category,
386                    const char *uri, const char *val, const char *default_val,
387                    void (*pr)(const char *buf, void *client_data),
388                    void *client_data,
389                    int errcode)
390 {
391     const char *res = 0;
392     const char *eval = val ? val : default_val;
393     const char *prefix = 0;
394
395     if (uri)
396     {
397         struct cql_prop_entry *e;
398
399         for (e = ct->entry; e; e = e->next)
400             if (!memcmp(e->pattern, "set.", 4) && e->value &&
401                 !strcmp(e->value, uri))
402             {
403                 prefix = e->pattern+4;
404                 break;
405             }
406         /* must have a prefix now - if not it's an error */
407     }
408
409     if (!uri || prefix)
410     {
411         if (!res)
412             res = cql_lookup_property(ct, category, prefix, eval);
413         /* we have some aliases for some relations unfortunately.. */
414         if (!res && !prefix && !strcmp(category, "relation"))
415         {
416             if (!strcmp(val, "=="))
417                 res = cql_lookup_property(ct, category, prefix, "exact");
418             if (!strcmp(val, "="))
419                 res = cql_lookup_property(ct, category, prefix, "eq");
420             if (!strcmp(val, "<="))
421                 res = cql_lookup_property(ct, category, prefix, "le");
422             if (!strcmp(val, ">="))
423                 res = cql_lookup_property(ct, category, prefix, "ge");
424         }
425         if (!res)
426             res = cql_lookup_property(ct, category, prefix, "*");
427     }
428     if (res)
429     {
430         char buf[64];
431
432         const char *cp0 = res, *cp1;
433         while ((cp1 = strchr(cp0, '=')))
434         {
435             int i;
436             while (*cp1 && *cp1 != ' ')
437                 cp1++;
438             if (cp1 - cp0 >= (ptrdiff_t) sizeof(buf))
439                 break;
440             memcpy(buf, cp0, cp1 - cp0);
441             buf[cp1-cp0] = 0;
442             (*pr)("@attr ", client_data);
443
444             for (i = 0; buf[i]; i++)
445             {
446                 if (buf[i] == '*')
447                     (*pr)(eval, client_data);
448                 else
449                 {
450                     char tmp[2];
451                     tmp[0] = buf[i];
452                     tmp[1] = '\0';
453                     (*pr)(tmp, client_data);
454                 }
455             }
456             (*pr)(" ", client_data);
457             cp0 = cp1;
458             while (*cp0 == ' ')
459                 cp0++;
460         }
461         return 1;
462     }
463     /* error ... */
464     if (errcode && !ct->error)
465     {
466         ct->error = errcode;
467         if (val)
468             ct->addinfo = xstrdup(val);
469         else
470             ct->addinfo = 0;
471     }
472     return 0;
473 }
474
475 int cql_pr_attr(cql_transform_t ct, const char *category,
476                 const char *val, const char *default_val,
477                 void (*pr)(const char *buf, void *client_data),
478                 void *client_data,
479                 int errcode)
480 {
481     return cql_pr_attr_uri(ct, category, 0 /* uri */,
482                            val, default_val, pr, client_data, errcode);
483 }
484
485
486 static void cql_pr_int(int val,
487                        void (*pr)(const char *buf, void *client_data),
488                        void *client_data)
489 {
490     char buf[21];              /* enough characters to 2^64 */
491     sprintf(buf, "%d", val);
492     (*pr)(buf, client_data);
493     (*pr)(" ", client_data);
494 }
495
496
497 static int cql_pr_prox(cql_transform_t ct, struct cql_node *mods,
498                        void (*pr)(const char *buf, void *client_data),
499                        void *client_data)
500 {
501     int exclusion = 0;
502     int distance = -1;
503     int ordered = 0;
504     int proxrel = 2;            /* less than or equal */
505     int unit = 2;               /* word */
506
507     while (mods)
508     {
509         const char *name = mods->u.st.index;
510         const char *term = mods->u.st.term;
511         const char *relation = mods->u.st.relation;
512
513         if (!strcmp(name, "distance")) {
514             distance = strtol(term, (char**) 0, 0);
515             if (!strcmp(relation, "="))
516                 proxrel = 3;
517             else if (!strcmp(relation, ">"))
518                 proxrel = 5;
519             else if (!strcmp(relation, "<"))
520                 proxrel = 1;
521             else if (!strcmp(relation, ">="))
522                 proxrel = 4;
523             else if (!strcmp(relation, "<="))
524                 proxrel = 2;
525             else if (!strcmp(relation, "<>"))
526                 proxrel = 6;
527             else
528             {
529                 ct->error = YAZ_SRW_UNSUPP_PROX_RELATION;
530                 ct->addinfo = xstrdup(relation);
531                 return 0;
532             }
533         }
534         else if (!strcmp(name, "ordered"))
535             ordered = 1;
536         else if (!strcmp(name, "unordered"))
537             ordered = 0;
538         else if (!strcmp(name, "unit"))
539         {
540             if (!strcmp(term, "word"))
541                 unit = 2;
542             else if (!strcmp(term, "sentence"))
543                 unit = 3;
544             else if (!strcmp(term, "paragraph"))
545                 unit = 4;
546             else if (!strcmp(term, "element"))
547                 unit = 8;
548             else
549             {
550                 ct->error = YAZ_SRW_UNSUPP_PROX_UNIT;
551                 ct->addinfo = xstrdup(term);
552                 return 0;
553             }
554         }
555         else
556         {
557             ct->error = YAZ_SRW_UNSUPP_BOOLEAN_MODIFIER;
558             ct->addinfo = xstrdup(name);
559             return 0;
560         }
561         mods = mods->u.st.modifiers;
562     }
563
564     if (distance == -1)
565         distance = (unit == 2) ? 1 : 0;
566
567     cql_pr_int(exclusion, pr, client_data);
568     cql_pr_int(distance, pr, client_data);
569     cql_pr_int(ordered, pr, client_data);
570     cql_pr_int(proxrel, pr, client_data);
571     (*pr)("k ", client_data);
572     cql_pr_int(unit, pr, client_data);
573
574     return 1;
575 }
576
577 /* ### checks for CQL relation-name rather than Type-1 attribute */
578 static int has_modifier(struct cql_node *cn, const char *name) {
579     struct cql_node *mod;
580     for (mod = cn->u.st.modifiers; mod != 0; mod = mod->u.st.modifiers) {
581         if (!strcmp(mod->u.st.index, name))
582             return 1;
583     }
584
585     return 0;
586 }
587
588 static void emit_term(cql_transform_t ct,
589                       struct cql_node *cn,
590                       const char *term, int length,
591                       void (*pr)(const char *buf, void *client_data),
592                       void *client_data)
593 {
594     int i;
595     const char *ns = cn->u.st.index_uri;
596     int z3958_mode = 0;
597     int process_term = 1;
598
599     if (has_modifier(cn, "regexp"))
600         process_term = 0;
601     else if (has_modifier(cn, "unmasked"))
602         process_term = 0;
603     else if (cql_lookup_property(ct, "truncation", 0, "cql"))
604     {
605         process_term = 0;
606         cql_pr_attr(ct, "truncation", "cql", 0,
607                     pr, client_data, YAZ_SRW_MASKING_CHAR_UNSUPP);
608     }
609     assert(cn->which == CQL_NODE_ST);
610
611     if (process_term)
612     {   /* convert term via truncation.things */
613         unsigned anchor = 0;
614         unsigned trunc = 0;
615         for (i = 0; i < length; i++)
616         {
617             if (term[i] == '\\' && i < length - 1)
618                 i++;
619             else
620             {
621                 switch (term[i])
622                 {
623                 case '^':
624                     if (i == 0)
625                         anchor |= 1;
626                     else if (i == length - 1)
627                         anchor |= 2;
628                     break;
629                 case '*':
630                     if (i == 0)
631                         trunc |= 1;
632                     else if (i == length - 1)
633                         trunc |= 2;
634                     else
635                         z3958_mode = 1;
636                     break;
637                 case '?':
638                     z3958_mode = 1;
639                     break;
640                 }
641             }
642         }
643         if (anchor == 3)
644         {
645             cql_pr_attr(ct, "position", "firstAndLast", 0,
646                         pr, client_data, YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
647             term++;
648             length -= 2;
649         }
650         else if (anchor == 1)
651         {
652             cql_pr_attr(ct, "position", "first", 0,
653                         pr, client_data, YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
654             term++;
655             length--;
656         }
657         else if (anchor == 2)
658         {
659             cql_pr_attr(ct, "position", "last", 0,
660                         pr, client_data, YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
661             length--;
662         }
663         else
664         {
665             cql_pr_attr(ct, "position", "any", 0,
666                         pr, client_data, YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
667         }
668         if (z3958_mode == 0)
669         {
670             if (trunc == 3 && cql_pr_attr(ct, "truncation",
671                                           "both", 0, pr, client_data, 0))
672             {
673                 term++;
674                 length -= 2;
675             }
676             else if (trunc == 1 && cql_pr_attr(ct, "truncation",
677                                                "left", 0, pr, client_data, 0))
678             {
679                 term++;
680                 length--;
681             }
682             else if (trunc == 2 && cql_pr_attr(ct, "truncation", "right", 0,
683                                                pr, client_data, 0))
684             {
685                 length--;
686             }
687             else if (trunc)
688                 z3958_mode = 1;
689             else
690                 cql_pr_attr(ct, "truncation", "none", 0,
691                             pr, client_data, 0);
692         }
693         if (z3958_mode)
694             cql_pr_attr(ct, "truncation", "z3958", 0,
695                         pr, client_data, YAZ_SRW_MASKING_CHAR_UNSUPP);
696     }
697     if (ns) {
698         cql_pr_attr_uri(ct, "index", ns,
699                         cn->u.st.index, "serverChoice",
700                         pr, client_data, YAZ_SRW_UNSUPP_INDEX);
701     }
702     if (cn->u.st.modifiers)
703     {
704         struct cql_node *mod = cn->u.st.modifiers;
705         for (; mod; mod = mod->u.st.modifiers)
706         {
707             cql_pr_attr(ct, "relationModifier", mod->u.st.index, 0,
708                         pr, client_data, YAZ_SRW_UNSUPP_RELATION_MODIFIER);
709         }
710     }
711     (*pr)("\"", client_data);
712     if (process_term)
713         for (i = 0; i < length; i++)
714         {
715             char x[2]; /* temp buffer */
716             if (term[i] == '\\' && i < length - 1)
717             {
718                 i++;
719                 if (strchr("\"\\", term[i]))
720                     pr("\\", client_data);
721                 if (z3958_mode && strchr("#?", term[i]))
722                     pr("\\\\", client_data); /* double \\ to survive PQF parse */
723                 x[0] = term[i];
724                 x[1] = '\0';
725                 pr(x, client_data);
726             }
727             else if (z3958_mode && term[i] == '*')
728             {
729                 pr("?", client_data);
730                 if (i < length - 1 && yaz_isdigit(term[i+1]))
731                     pr("\\\\", client_data); /* dbl \\ to survive PQF parse */
732             }
733             else if (z3958_mode && term[i] == '?')
734             {
735                 pr("#", client_data);
736             }
737             else
738             {
739                 if (term[i] == '\"')
740                     pr("\\", client_data);
741                 if (z3958_mode && strchr("#?", term[i]))
742                     pr("\\\\", client_data); /* dbl \\ to survive PQF parse */
743                 x[0] = term[i];
744                 x[1] = '\0';
745                 pr(x, client_data);
746             }
747         }
748     else
749     {
750         for (i = 0; i < length; i++)
751         {
752             char x[2];
753             x[0] = term[i];
754             x[1] = '\0';
755             pr(x, client_data);
756         }
757     }
758     (*pr)("\" ", client_data);
759 }
760
761 static void emit_terms(cql_transform_t ct,
762                        struct cql_node *cn,
763                        void (*pr)(const char *buf, void *client_data),
764                        void *client_data,
765                        const char *op)
766 {
767     struct cql_node *ne = cn->u.st.extra_terms;
768     if (ne)
769     {
770         (*pr)("@", client_data);
771         (*pr)(op, client_data);
772         (*pr)(" ", client_data);
773     }
774     emit_term(ct, cn, cn->u.st.term, strlen(cn->u.st.term),
775               pr, client_data);
776     for (; ne; ne = ne->u.st.extra_terms)
777     {
778         if (ne->u.st.extra_terms)
779         {
780             (*pr)("@", client_data);
781             (*pr)(op, client_data);
782             (*pr)(" ", client_data);
783         }
784         emit_term(ct, cn, ne->u.st.term, strlen(ne->u.st.term),
785                   pr, client_data);
786     }
787 }
788
789 static void emit_wordlist(cql_transform_t ct,
790                           struct cql_node *cn,
791                           void (*pr)(const char *buf, void *client_data),
792                           void *client_data,
793                           const char *op)
794 {
795     const char *cp0 = cn->u.st.term;
796     const char *cp1;
797     const char *last_term = 0;
798     int last_length = 0;
799     while(cp0)
800     {
801         while (*cp0 == ' ')
802             cp0++;
803         cp1 = strchr(cp0, ' ');
804         if (last_term)
805         {
806             (*pr)("@", client_data);
807             (*pr)(op, client_data);
808             (*pr)(" ", client_data);
809             emit_term(ct, cn, last_term, last_length, pr, client_data);
810         }
811         last_term = cp0;
812         if (cp1)
813             last_length = cp1 - cp0;
814         else
815             last_length = strlen(cp0);
816         cp0 = cp1;
817     }
818     if (last_term)
819         emit_term(ct, cn, last_term, last_length, pr, client_data);
820 }
821
822 void cql_transform_r(cql_transform_t ct,
823                      struct cql_node *cn,
824                      void (*pr)(const char *buf, void *client_data),
825                      void *client_data)
826 {
827     const char *ns;
828     struct cql_node *mods;
829
830     if (!cn)
831         return;
832     switch (cn->which)
833     {
834     case CQL_NODE_ST:
835         ns = cn->u.st.index_uri;
836         if (ns)
837         {
838             if (!strcmp(ns, cql_uri())
839                 && cn->u.st.index && !cql_strcmp(cn->u.st.index, "resultSet"))
840             {
841                 (*pr)("@set \"", client_data);
842                 (*pr)(cn->u.st.term, client_data);
843                 (*pr)("\" ", client_data);
844                 return ;
845             }
846         }
847         else
848         {
849             if (!ct->error)
850             {
851                 ct->error = YAZ_SRW_UNSUPP_CONTEXT_SET;
852                 ct->addinfo = 0;
853             }
854         }
855         cql_pr_attr(ct, "always", 0, 0, pr, client_data, 0);
856         cql_pr_attr(ct, "relation", cn->u.st.relation, 0, pr, client_data,
857                     YAZ_SRW_UNSUPP_RELATION);
858         cql_pr_attr(ct, "structure", cn->u.st.relation, 0,
859                     pr, client_data, YAZ_SRW_UNSUPP_COMBI_OF_RELATION_AND_TERM);
860         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "all"))
861             emit_wordlist(ct, cn, pr, client_data, "and");
862         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "any"))
863             emit_wordlist(ct, cn, pr, client_data, "or");
864         else
865             emit_terms(ct, cn, pr, client_data, "and");
866         break;
867     case CQL_NODE_BOOL:
868         (*pr)("@", client_data);
869         (*pr)(cn->u.boolean.value, client_data);
870         (*pr)(" ", client_data);
871         mods = cn->u.boolean.modifiers;
872         if (!strcmp(cn->u.boolean.value, "prox"))
873         {
874             if (!cql_pr_prox(ct, mods, pr, client_data))
875                 return;
876         }
877         else if (mods)
878         {
879             /* Boolean modifiers other than on proximity not supported */
880             ct->error = YAZ_SRW_UNSUPP_BOOLEAN_MODIFIER;
881             ct->addinfo = xstrdup(mods->u.st.index);
882             return;
883         }
884
885         cql_transform_r(ct, cn->u.boolean.left, pr, client_data);
886         cql_transform_r(ct, cn->u.boolean.right, pr, client_data);
887         break;
888     case CQL_NODE_SORT:
889         cql_transform_r(ct, cn->u.sort.search, pr, client_data);
890         break;
891     default:
892         fprintf(stderr, "Fatal: impossible CQL node-type %d\n", cn->which);
893         abort();
894     }
895 }
896
897 int cql_transform(cql_transform_t ct, struct cql_node *cn,
898                   void (*pr)(const char *buf, void *client_data),
899                   void *client_data)
900 {
901     struct cql_prop_entry *e;
902     NMEM nmem = nmem_create();
903
904     ct->error = 0;
905     xfree(ct->addinfo);
906     ct->addinfo = 0;
907
908     for (e = ct->entry; e ; e = e->next)
909     {
910         if (!cql_strncmp(e->pattern, "set.", 4))
911             cql_apply_prefix(nmem, cn, e->pattern+4, e->value);
912         else if (!cql_strcmp(e->pattern, "set"))
913             cql_apply_prefix(nmem, cn, 0, e->value);
914     }
915     cql_transform_r(ct, cn, pr, client_data);
916     nmem_destroy(nmem);
917     return ct->error;
918 }
919
920
921 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
922 {
923     return cql_transform(ct, cn, cql_fputs, f);
924 }
925
926 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
927                       char *out, int max)
928 {
929     struct cql_buf_write_info info;
930     int r;
931
932     info.off = 0;
933     info.max = max;
934     info.buf = out;
935     r = cql_transform(ct, cn, cql_buf_write_handler, &info);
936     if (info.off < 0) {
937         /* Attempt to write past end of buffer.  For some reason, this
938            SRW diagnostic is deprecated, but it's so perfect for our
939            purposes that it would be stupid not to use it. */
940         char numbuf[30];
941         ct->error = YAZ_SRW_TOO_MANY_CHARS_IN_QUERY;
942         sprintf(numbuf, "%ld", (long) info.max);
943         ct->addinfo = xstrdup(numbuf);
944         return -1;
945     }
946     if (info.off >= 0)
947         info.buf[info.off] = '\0';
948     return r;
949 }
950
951 int cql_transform_error(cql_transform_t ct, const char **addinfo)
952 {
953     *addinfo = ct->addinfo;
954     return ct->error;
955 }
956
957 void cql_transform_set_error(cql_transform_t ct, int error, const char *addinfo)
958 {
959     xfree(ct->addinfo);
960     ct->addinfo = addinfo ? xstrdup(addinfo) : 0;
961     ct->error = error;
962 }
963
964 /*
965  * Local variables:
966  * c-basic-offset: 4
967  * c-file-style: "Stroustrup"
968  * indent-tabs-mode: nil
969  * End:
970  * vim: shiftwidth=4 tabstop=8 expandtab
971  */
972