Improve display of MARC records with multi-byte subfield IDs YAZ-695
[yaz-moved-to-github.git] / src / cqltransform.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2013 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     WRBUF w;
49     NMEM nmem;
50 };
51
52
53 cql_transform_t cql_transform_create(void)
54 {
55     cql_transform_t ct = (cql_transform_t) xmalloc(sizeof(*ct));
56     ct->tok_cfg = yaz_tok_cfg_create();
57     ct->w = wrbuf_alloc();
58     ct->error = 0;
59     ct->addinfo = 0;
60     ct->entry = 0;
61     ct->nmem = nmem_create();
62     return ct;
63 }
64
65 static int cql_transform_parse_tok_line(cql_transform_t ct,
66                                         const char *pattern,
67                                         yaz_tok_parse_t tp)
68 {
69     int ae_num = 0;
70     Z_AttributeElement *ae[20];
71     int ret = 0; /* 0=OK, != 0 FAIL */
72     int t;
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(ct->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(ct->w, " ");
99             wrbuf_puts(ct->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(ct->w, "=");
163         wrbuf_puts(ct->w, yaz_tok_parse_string(tp));
164         t = yaz_tok_move(tp);
165         wrbuf_puts(ct->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(ct->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     return ret;
201 }
202
203 int cql_transform_define_pattern(cql_transform_t ct, const char *pattern,
204                                  const char *value)
205 {
206     int r;
207     yaz_tok_parse_t tp = yaz_tok_parse_buf(ct->tok_cfg, value);
208     yaz_tok_cfg_single_tokens(ct->tok_cfg, "=");
209     r = cql_transform_parse_tok_line(ct, pattern, tp);
210     yaz_tok_parse_destroy(tp);
211     return r;
212 }
213
214 cql_transform_t cql_transform_open_FILE(FILE *f)
215 {
216     cql_transform_t ct = cql_transform_create();
217     char line[1024];
218
219     yaz_tok_cfg_single_tokens(ct->tok_cfg, "=");
220
221     while (fgets(line, sizeof(line)-1, f))
222     {
223         yaz_tok_parse_t tp = yaz_tok_parse_buf(ct->tok_cfg, line);
224         int t;
225         wrbuf_rewind(ct->w);
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     wrbuf_destroy(ct->w);
273     nmem_destroy(ct->nmem);
274     xfree(ct);
275 }
276
277 cql_transform_t cql_transform_open_fname(const char *fname)
278 {
279     cql_transform_t ct;
280     FILE *f = fopen(fname, "r");
281     if (!f)
282         return 0;
283     ct = cql_transform_open_FILE(f);
284     fclose(f);
285     return ct;
286 }
287
288 #if 0
289 struct Z_AttributeElement {
290         Z_AttributeSetId *attributeSet; /* OPT */
291         int *attributeType;
292         int which;
293         union {
294                 int *numeric;
295                 Z_ComplexAttribute *complex;
296 #define Z_AttributeValue_numeric 1
297 #define Z_AttributeValue_complex 2
298         } value;
299 };
300 #endif
301
302 static int compare_attr(Z_AttributeElement *a, Z_AttributeElement *b)
303 {
304     ODR odr_a = odr_createmem(ODR_ENCODE);
305     ODR odr_b = odr_createmem(ODR_ENCODE);
306     int len_a, len_b;
307     char *buf_a, *buf_b;
308     int ret;
309
310     z_AttributeElement(odr_a, &a, 0, 0);
311     z_AttributeElement(odr_b, &b, 0, 0);
312
313     buf_a = odr_getbuf(odr_a, &len_a, 0);
314     buf_b = odr_getbuf(odr_b, &len_b, 0);
315
316     ret = yaz_memcmp(buf_a, buf_b, len_a, len_b);
317
318     odr_destroy(odr_a);
319     odr_destroy(odr_b);
320     return ret;
321 }
322
323 const char *cql_lookup_reverse(cql_transform_t ct,
324                                const char *category,
325                                Z_AttributeList *attributes)
326 {
327     struct cql_prop_entry *e;
328     size_t clen = strlen(category);
329     for (e = ct->entry; e; e = e->next)
330     {
331         if (!strncmp(e->pattern, category, clen))
332         {
333             /* category matches.. See if attributes in pattern value
334                are all listed in actual attributes */
335             int i;
336             for (i = 0; i < e->attr_list.num_attributes; i++)
337             {
338                 /* entry attribute */
339                 Z_AttributeElement *e_ae = e->attr_list.attributes[i];
340                 int j;
341                 for (j = 0; j < attributes->num_attributes; j++)
342                 {
343                     /* actual attribute */
344                     Z_AttributeElement *a_ae = attributes->attributes[j];
345                     int r = compare_attr(e_ae, a_ae);
346                     if (r == 0)
347                         break;
348                 }
349                 if (j == attributes->num_attributes)
350                     break; /* i was not found at all.. try next pattern */
351
352             }
353             if (i == e->attr_list.num_attributes)
354                 return e->pattern + clen;
355         }
356     }
357     return 0;
358 }
359
360 static const char *cql_lookup_property(cql_transform_t ct,
361                                        const char *pat1, const char *pat2,
362                                        const char *pat3)
363 {
364     char pattern[120];
365     struct cql_prop_entry *e;
366
367     if (pat1 && pat2 && pat3)
368         sprintf(pattern, "%.39s.%.39s.%.39s", pat1, pat2, pat3);
369     else if (pat1 && pat2)
370         sprintf(pattern, "%.39s.%.39s", pat1, pat2);
371     else if (pat1 && pat3)
372         sprintf(pattern, "%.39s.%.39s", pat1, pat3);
373     else if (pat1)
374         sprintf(pattern, "%.39s", pat1);
375     else
376         return 0;
377
378     for (e = ct->entry; e; e = e->next)
379     {
380         if (!cql_strcmp(e->pattern, pattern))
381             return e->value;
382     }
383     return 0;
384 }
385
386 int cql_pr_attr_uri(cql_transform_t ct, const char *category,
387                    const char *uri, const char *val, const char *default_val,
388                    void (*pr)(const char *buf, void *client_data),
389                    void *client_data,
390                    int errcode)
391 {
392     const char *res = 0;
393     const char *eval = val ? val : default_val;
394     const char *prefix = 0;
395
396     if (uri)
397     {
398         struct cql_prop_entry *e;
399
400         for (e = ct->entry; e; e = e->next)
401             if (!memcmp(e->pattern, "set.", 4) && e->value &&
402                 !strcmp(e->value, uri))
403             {
404                 prefix = e->pattern+4;
405                 break;
406             }
407         /* must have a prefix now - if not it's an error */
408     }
409
410     if (!uri || prefix)
411     {
412         if (!res)
413             res = cql_lookup_property(ct, category, prefix, eval);
414         /* we have some aliases for some relations unfortunately.. */
415         if (!res && !prefix && !strcmp(category, "relation"))
416         {
417             if (!strcmp(val, "=="))
418                 res = cql_lookup_property(ct, category, prefix, "exact");
419             if (!strcmp(val, "="))
420                 res = cql_lookup_property(ct, category, prefix, "eq");
421             if (!strcmp(val, "<="))
422                 res = cql_lookup_property(ct, category, prefix, "le");
423             if (!strcmp(val, ">="))
424                 res = cql_lookup_property(ct, category, prefix, "ge");
425         }
426         if (!res)
427             res = cql_lookup_property(ct, category, prefix, "*");
428     }
429     if (res)
430     {
431         char buf[64];
432
433         const char *cp0 = res, *cp1;
434         while ((cp1 = strchr(cp0, '=')))
435         {
436             int i;
437             while (*cp1 && *cp1 != ' ')
438                 cp1++;
439             if (cp1 - cp0 >= (ptrdiff_t) sizeof(buf))
440                 break;
441             memcpy(buf, cp0, cp1 - cp0);
442             buf[cp1-cp0] = 0;
443             (*pr)("@attr ", client_data);
444
445             for (i = 0; buf[i]; i++)
446             {
447                 if (buf[i] == '*')
448                     (*pr)(eval, client_data);
449                 else
450                 {
451                     char tmp[2];
452                     tmp[0] = buf[i];
453                     tmp[1] = '\0';
454                     (*pr)(tmp, client_data);
455                 }
456             }
457             (*pr)(" ", client_data);
458             cp0 = cp1;
459             while (*cp0 == ' ')
460                 cp0++;
461         }
462         return 1;
463     }
464     /* error ... */
465     if (errcode && !ct->error)
466     {
467         ct->error = errcode;
468         if (val)
469             ct->addinfo = xstrdup(val);
470         else
471             ct->addinfo = 0;
472     }
473     return 0;
474 }
475
476 int cql_pr_attr(cql_transform_t ct, const char *category,
477                 const char *val, const char *default_val,
478                 void (*pr)(const char *buf, void *client_data),
479                 void *client_data,
480                 int errcode)
481 {
482     return cql_pr_attr_uri(ct, category, 0 /* uri */,
483                            val, default_val, pr, client_data, errcode);
484 }
485
486
487 static void cql_pr_int(int val,
488                        void (*pr)(const char *buf, void *client_data),
489                        void *client_data)
490 {
491     char buf[21];              /* enough characters to 2^64 */
492     sprintf(buf, "%d", val);
493     (*pr)(buf, client_data);
494     (*pr)(" ", client_data);
495 }
496
497
498 static int cql_pr_prox(cql_transform_t ct, struct cql_node *mods,
499                        void (*pr)(const char *buf, void *client_data),
500                        void *client_data)
501 {
502     int exclusion = 0;
503     int distance = -1;
504     int ordered = 0;
505     int proxrel = 2;            /* less than or equal */
506     int unit = 2;               /* word */
507
508     while (mods)
509     {
510         const char *name = mods->u.st.index;
511         const char *term = mods->u.st.term;
512         const char *relation = mods->u.st.relation;
513
514         if (!strcmp(name, "distance")) {
515             distance = strtol(term, (char**) 0, 0);
516             if (!strcmp(relation, "="))
517                 proxrel = 3;
518             else if (!strcmp(relation, ">"))
519                 proxrel = 5;
520             else if (!strcmp(relation, "<"))
521                 proxrel = 1;
522             else if (!strcmp(relation, ">="))
523                 proxrel = 4;
524             else if (!strcmp(relation, "<="))
525                 proxrel = 2;
526             else if (!strcmp(relation, "<>"))
527                 proxrel = 6;
528             else
529             {
530                 ct->error = YAZ_SRW_UNSUPP_PROX_RELATION;
531                 ct->addinfo = xstrdup(relation);
532                 return 0;
533             }
534         }
535         else if (!strcmp(name, "ordered"))
536             ordered = 1;
537         else if (!strcmp(name, "unordered"))
538             ordered = 0;
539         else if (!strcmp(name, "unit"))
540         {
541             if (!strcmp(term, "word"))
542                 unit = 2;
543             else if (!strcmp(term, "sentence"))
544                 unit = 3;
545             else if (!strcmp(term, "paragraph"))
546                 unit = 4;
547             else if (!strcmp(term, "element"))
548                 unit = 8;
549             else
550             {
551                 ct->error = YAZ_SRW_UNSUPP_PROX_UNIT;
552                 ct->addinfo = xstrdup(term);
553                 return 0;
554             }
555         }
556         else
557         {
558             ct->error = YAZ_SRW_UNSUPP_BOOLEAN_MODIFIER;
559             ct->addinfo = xstrdup(name);
560             return 0;
561         }
562         mods = mods->u.st.modifiers;
563     }
564
565     if (distance == -1)
566         distance = (unit == 2) ? 1 : 0;
567
568     cql_pr_int(exclusion, pr, client_data);
569     cql_pr_int(distance, pr, client_data);
570     cql_pr_int(ordered, pr, client_data);
571     cql_pr_int(proxrel, pr, client_data);
572     (*pr)("k ", client_data);
573     cql_pr_int(unit, pr, client_data);
574
575     return 1;
576 }
577
578 /* ### checks for CQL relation-name rather than Type-1 attribute */
579 static int has_modifier(struct cql_node *cn, const char *name) {
580     struct cql_node *mod;
581     for (mod = cn->u.st.modifiers; mod != 0; mod = mod->u.st.modifiers) {
582         if (!strcmp(mod->u.st.index, name))
583             return 1;
584     }
585
586     return 0;
587 }
588
589 static void emit_term(cql_transform_t ct,
590                       struct cql_node *cn,
591                       const char *term, int length,
592                       void (*pr)(const char *buf, void *client_data),
593                       void *client_data)
594 {
595     int i;
596     const char *ns = cn->u.st.index_uri;
597     int z3958_mode = 0;
598     int process_term = 1;
599
600     if (has_modifier(cn, "regexp"))
601         process_term = 0;
602     else if (has_modifier(cn, "unmasked"))
603         process_term = 0;
604     else if (cql_lookup_property(ct, "truncation", 0, "cql"))
605     {
606         process_term = 0;
607         cql_pr_attr(ct, "truncation", "cql", 0,
608                     pr, client_data, YAZ_SRW_MASKING_CHAR_UNSUPP);
609     }
610     assert(cn->which == CQL_NODE_ST);
611
612     if (process_term)
613     {   /* convert term via truncation.things */
614         unsigned anchor = 0;
615         unsigned trunc = 0;
616         for (i = 0; i < length; i++)
617         {
618             if (term[i] == '\\' && i < length - 1)
619                 i++;
620             else
621             {
622                 switch (term[i])
623                 {
624                 case '^':
625                     if (i == 0)
626                         anchor |= 1;
627                     else if (i == length - 1)
628                         anchor |= 2;
629                     break;
630                 case '*':
631                     if (i == 0)
632                         trunc |= 1;
633                     else if (i == length - 1)
634                         trunc |= 2;
635                     else
636                         z3958_mode = 1;
637                     break;
638                 case '?':
639                     z3958_mode = 1;
640                     break;
641                 }
642             }
643         }
644         if (anchor == 3)
645         {
646             cql_pr_attr(ct, "position", "firstAndLast", 0,
647                         pr, client_data, YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
648             term++;
649             length -= 2;
650         }
651         else if (anchor == 1)
652         {
653             cql_pr_attr(ct, "position", "first", 0,
654                         pr, client_data, YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
655             term++;
656             length--;
657         }
658         else if (anchor == 2)
659         {
660             cql_pr_attr(ct, "position", "last", 0,
661                         pr, client_data, YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
662             length--;
663         }
664         else
665         {
666             cql_pr_attr(ct, "position", "any", 0,
667                         pr, client_data, YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
668         }
669         if (z3958_mode == 0)
670         {
671             if (trunc == 3 && cql_pr_attr(ct, "truncation",
672                                           "both", 0, pr, client_data, 0))
673             {
674                 term++;
675                 length -= 2;
676             }
677             else if (trunc == 1 && cql_pr_attr(ct, "truncation",
678                                                "left", 0, pr, client_data, 0))
679             {
680                 term++;
681                 length--;
682             }
683             else if (trunc == 2 && cql_pr_attr(ct, "truncation", "right", 0,
684                                                pr, client_data, 0))
685             {
686                 length--;
687             }
688             else if (trunc)
689                 z3958_mode = 1;
690             else
691                 cql_pr_attr(ct, "truncation", "none", 0,
692                             pr, client_data, 0);
693         }
694         if (z3958_mode)
695             cql_pr_attr(ct, "truncation", "z3958", 0,
696                         pr, client_data, YAZ_SRW_MASKING_CHAR_UNSUPP);
697     }
698     if (ns) {
699         cql_pr_attr_uri(ct, "index", ns,
700                         cn->u.st.index, "serverChoice",
701                         pr, client_data, YAZ_SRW_UNSUPP_INDEX);
702     }
703     if (cn->u.st.modifiers)
704     {
705         struct cql_node *mod = cn->u.st.modifiers;
706         for (; mod; mod = mod->u.st.modifiers)
707         {
708             cql_pr_attr(ct, "relationModifier", mod->u.st.index, 0,
709                         pr, client_data, YAZ_SRW_UNSUPP_RELATION_MODIFIER);
710         }
711     }
712     (*pr)("\"", client_data);
713     if (process_term)
714         for (i = 0; i < length; i++)
715         {
716             char x[2]; /* temp buffer */
717             if (term[i] == '\\' && i < length - 1)
718             {
719                 i++;
720                 if (strchr("\"\\", term[i]))
721                     pr("\\", client_data);
722                 if (z3958_mode && strchr("#?", term[i]))
723                     pr("\\\\", client_data); /* double \\ to survive PQF parse */
724                 x[0] = term[i];
725                 x[1] = '\0';
726                 pr(x, client_data);
727             }
728             else if (z3958_mode && term[i] == '*')
729             {
730                 pr("?", client_data);
731                 if (i < length - 1 && yaz_isdigit(term[i+1]))
732                     pr("\\\\", client_data); /* dbl \\ to survive PQF parse */
733             }
734             else if (z3958_mode && term[i] == '?')
735             {
736                 pr("#", client_data);
737             }
738             else
739             {
740                 if (term[i] == '\"')
741                     pr("\\", client_data);
742                 if (z3958_mode && strchr("#?", term[i]))
743                     pr("\\\\", client_data); /* dbl \\ to survive PQF parse */
744                 x[0] = term[i];
745                 x[1] = '\0';
746                 pr(x, client_data);
747             }
748         }
749     else
750     {
751         for (i = 0; i < length; i++)
752         {
753             char x[2];
754             x[0] = term[i];
755             x[1] = '\0';
756             pr(x, client_data);
757         }
758     }
759     (*pr)("\" ", client_data);
760 }
761
762 static void emit_terms(cql_transform_t ct,
763                        struct cql_node *cn,
764                        void (*pr)(const char *buf, void *client_data),
765                        void *client_data,
766                        const char *op)
767 {
768     struct cql_node *ne = cn->u.st.extra_terms;
769     if (ne)
770     {
771         (*pr)("@", client_data);
772         (*pr)(op, client_data);
773         (*pr)(" ", client_data);
774     }
775     emit_term(ct, cn, cn->u.st.term, strlen(cn->u.st.term),
776               pr, client_data);
777     for (; ne; ne = ne->u.st.extra_terms)
778     {
779         if (ne->u.st.extra_terms)
780         {
781             (*pr)("@", client_data);
782             (*pr)(op, client_data);
783             (*pr)(" ", client_data);
784         }
785         emit_term(ct, cn, ne->u.st.term, strlen(ne->u.st.term),
786                   pr, client_data);
787     }
788 }
789
790 static void emit_wordlist(cql_transform_t ct,
791                           struct cql_node *cn,
792                           void (*pr)(const char *buf, void *client_data),
793                           void *client_data,
794                           const char *op)
795 {
796     const char *cp0 = cn->u.st.term;
797     const char *cp1;
798     const char *last_term = 0;
799     int last_length = 0;
800     while(cp0)
801     {
802         while (*cp0 == ' ')
803             cp0++;
804         cp1 = strchr(cp0, ' ');
805         if (last_term)
806         {
807             (*pr)("@", client_data);
808             (*pr)(op, client_data);
809             (*pr)(" ", client_data);
810             emit_term(ct, cn, last_term, last_length, pr, client_data);
811         }
812         last_term = cp0;
813         if (cp1)
814             last_length = cp1 - cp0;
815         else
816             last_length = strlen(cp0);
817         cp0 = cp1;
818     }
819     if (last_term)
820         emit_term(ct, cn, last_term, last_length, pr, client_data);
821 }
822
823 void cql_transform_r(cql_transform_t ct,
824                      struct cql_node *cn,
825                      void (*pr)(const char *buf, void *client_data),
826                      void *client_data)
827 {
828     const char *ns;
829     struct cql_node *mods;
830
831     if (!cn)
832         return;
833     switch (cn->which)
834     {
835     case CQL_NODE_ST:
836         ns = cn->u.st.index_uri;
837         if (ns)
838         {
839             if (!strcmp(ns, cql_uri())
840                 && cn->u.st.index && !cql_strcmp(cn->u.st.index, "resultSet"))
841             {
842                 (*pr)("@set \"", client_data);
843                 (*pr)(cn->u.st.term, client_data);
844                 (*pr)("\" ", client_data);
845                 return ;
846             }
847         }
848         else
849         {
850             if (!ct->error)
851             {
852                 ct->error = YAZ_SRW_UNSUPP_CONTEXT_SET;
853                 ct->addinfo = 0;
854             }
855         }
856         cql_pr_attr(ct, "always", 0, 0, pr, client_data, 0);
857         cql_pr_attr(ct, "relation", cn->u.st.relation, 0, pr, client_data,
858                     YAZ_SRW_UNSUPP_RELATION);
859         cql_pr_attr(ct, "structure", cn->u.st.relation, 0,
860                     pr, client_data, YAZ_SRW_UNSUPP_COMBI_OF_RELATION_AND_TERM);
861         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "all"))
862             emit_wordlist(ct, cn, pr, client_data, "and");
863         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "any"))
864             emit_wordlist(ct, cn, pr, client_data, "or");
865         else
866             emit_terms(ct, cn, pr, client_data, "and");
867         break;
868     case CQL_NODE_BOOL:
869         (*pr)("@", client_data);
870         (*pr)(cn->u.boolean.value, client_data);
871         (*pr)(" ", client_data);
872         mods = cn->u.boolean.modifiers;
873         if (!strcmp(cn->u.boolean.value, "prox"))
874         {
875             if (!cql_pr_prox(ct, mods, pr, client_data))
876                 return;
877         }
878         else if (mods)
879         {
880             /* Boolean modifiers other than on proximity not supported */
881             ct->error = YAZ_SRW_UNSUPP_BOOLEAN_MODIFIER;
882             ct->addinfo = xstrdup(mods->u.st.index);
883             return;
884         }
885
886         cql_transform_r(ct, cn->u.boolean.left, pr, client_data);
887         cql_transform_r(ct, cn->u.boolean.right, pr, client_data);
888         break;
889     case CQL_NODE_SORT:
890         cql_transform_r(ct, cn->u.sort.search, pr, client_data);
891         break;
892     default:
893         fprintf(stderr, "Fatal: impossible CQL node-type %d\n", cn->which);
894         abort();
895     }
896 }
897
898 int cql_transform(cql_transform_t ct, struct cql_node *cn,
899                   void (*pr)(const char *buf, void *client_data),
900                   void *client_data)
901 {
902     struct cql_prop_entry *e;
903     NMEM nmem = nmem_create();
904
905     ct->error = 0;
906     xfree(ct->addinfo);
907     ct->addinfo = 0;
908
909     for (e = ct->entry; e ; e = e->next)
910     {
911         if (!cql_strncmp(e->pattern, "set.", 4))
912             cql_apply_prefix(nmem, cn, e->pattern+4, e->value);
913         else if (!cql_strcmp(e->pattern, "set"))
914             cql_apply_prefix(nmem, cn, 0, e->value);
915     }
916     cql_transform_r(ct, cn, pr, client_data);
917     nmem_destroy(nmem);
918     return ct->error;
919 }
920
921
922 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
923 {
924     return cql_transform(ct, cn, cql_fputs, f);
925 }
926
927 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
928                       char *out, int max)
929 {
930     struct cql_buf_write_info info;
931     int r;
932
933     info.off = 0;
934     info.max = max;
935     info.buf = out;
936     r = cql_transform(ct, cn, cql_buf_write_handler, &info);
937     if (info.off < 0) {
938         /* Attempt to write past end of buffer.  For some reason, this
939            SRW diagnostic is deprecated, but it's so perfect for our
940            purposes that it would be stupid not to use it. */
941         char numbuf[30];
942         ct->error = YAZ_SRW_TOO_MANY_CHARS_IN_QUERY;
943         sprintf(numbuf, "%ld", (long) info.max);
944         ct->addinfo = xstrdup(numbuf);
945         return -1;
946     }
947     if (info.off >= 0)
948         info.buf[info.off] = '\0';
949     return r;
950 }
951
952 int cql_transform_error(cql_transform_t ct, const char **addinfo)
953 {
954     *addinfo = ct->addinfo;
955     return ct->error;
956 }
957
958 void cql_transform_set_error(cql_transform_t ct, int error, const char *addinfo)
959 {
960     xfree(ct->addinfo);
961     ct->addinfo = addinfo ? xstrdup(addinfo) : 0;
962     ct->error = error;
963 }
964
965 /*
966  * Local variables:
967  * c-basic-offset: 4
968  * c-file-style: "Stroustrup"
969  * indent-tabs-mode: nil
970  * End:
971  * vim: shiftwidth=4 tabstop=8 expandtab
972  */
973