CCL: fix other inherited attributes
[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     WRBUF 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 = wrbuf_alloc();
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_noclose(pr, yaz_log_file());
195             z_AttributeList(pr, &alp, 0, 0);
196             odr_destroy(pr);
197         }
198     }
199     wrbuf_destroy(w);
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         t = yaz_tok_move(tp);
226         if (t == YAZ_TOK_STRING)
227         {
228             char * pattern = xstrdup(yaz_tok_parse_string(tp));
229             t = yaz_tok_move(tp);
230             if (t != '=')
231             {
232                 yaz_tok_parse_destroy(tp);
233                 cql_transform_close(ct);
234                 return 0;
235             }
236             if (cql_transform_parse_tok_line(ct, pattern, tp))
237             {
238                 yaz_tok_parse_destroy(tp);
239                 cql_transform_close(ct);
240                 return 0;
241             }
242             xfree(pattern);
243         }
244         else if (t != YAZ_TOK_EOF)
245         {
246             yaz_tok_parse_destroy(tp);
247             cql_transform_close(ct);
248             return 0;
249         }
250         yaz_tok_parse_destroy(tp);
251     }
252     return ct;
253 }
254
255 void cql_transform_close(cql_transform_t ct)
256 {
257     struct cql_prop_entry *pe;
258     if (!ct)
259         return;
260     pe = ct->entry;
261     while (pe)
262     {
263         struct cql_prop_entry *pe_next = pe->next;
264         xfree(pe->pattern);
265         xfree(pe->value);
266         xfree(pe);
267         pe = pe_next;
268     }
269     wrbuf_destroy(ct->addinfo);
270     yaz_tok_cfg_destroy(ct->tok_cfg);
271     nmem_destroy(ct->nmem);
272     xfree(ct);
273 }
274
275 cql_transform_t cql_transform_open_fname(const char *fname)
276 {
277     cql_transform_t ct;
278     FILE *f = fopen(fname, "r");
279     if (!f)
280         return 0;
281     ct = cql_transform_open_FILE(f);
282     fclose(f);
283     return ct;
284 }
285
286 #if 0
287 struct Z_AttributeElement {
288         Z_AttributeSetId *attributeSet; /* OPT */
289         int *attributeType;
290         int which;
291         union {
292                 int *numeric;
293                 Z_ComplexAttribute *complex;
294 #define Z_AttributeValue_numeric 1
295 #define Z_AttributeValue_complex 2
296         } value;
297 };
298 #endif
299
300 static int compare_attr(Z_AttributeElement *a, Z_AttributeElement *b)
301 {
302     ODR odr_a = odr_createmem(ODR_ENCODE);
303     ODR odr_b = odr_createmem(ODR_ENCODE);
304     int len_a, len_b;
305     char *buf_a, *buf_b;
306     int ret;
307
308     z_AttributeElement(odr_a, &a, 0, 0);
309     z_AttributeElement(odr_b, &b, 0, 0);
310
311     buf_a = odr_getbuf(odr_a, &len_a, 0);
312     buf_b = odr_getbuf(odr_b, &len_b, 0);
313
314     ret = yaz_memcmp(buf_a, buf_b, len_a, len_b);
315
316     odr_destroy(odr_a);
317     odr_destroy(odr_b);
318     return ret;
319 }
320
321 const char *cql_lookup_reverse(cql_transform_t ct,
322                                const char *category,
323                                Z_AttributeList *attributes)
324 {
325     struct cql_prop_entry *e;
326     size_t clen = strlen(category);
327     for (e = ct->entry; e; e = e->next)
328     {
329         if (!strncmp(e->pattern, category, clen))
330         {
331             /* category matches.. See if attributes in pattern value
332                are all listed in actual attributes */
333             int i;
334             for (i = 0; i < e->attr_list.num_attributes; i++)
335             {
336                 /* entry attribute */
337                 Z_AttributeElement *e_ae = e->attr_list.attributes[i];
338                 int j;
339                 for (j = 0; j < attributes->num_attributes; j++)
340                 {
341                     /* actual attribute */
342                     Z_AttributeElement a_ae = *attributes->attributes[j];
343                     if (a_ae.attributeSet && &e_ae->attributeSet &&
344                         !oid_oidcmp(a_ae.attributeSet, yaz_oid_attset_bib_1))
345                         a_ae.attributeSet = 0;
346                     if (!compare_attr(e_ae, &a_ae))
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, WRBUF addinfo, 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 0;
463     }
464     /* error ... */
465     if (errcode == 0)
466         return 1; /* signal error, but do not set addinfo */
467     if (val)
468         wrbuf_puts(addinfo, val);
469     return errcode;
470 }
471
472 int cql_pr_attr(cql_transform_t ct, WRBUF addinfo, const char *category,
473                 const char *val, const char *default_val,
474                 void (*pr)(const char *buf, void *client_data),
475                 void *client_data,
476                 int errcode)
477 {
478     return cql_pr_attr_uri(ct, addinfo, category, 0 /* uri */,
479                            val, default_val, pr, client_data, errcode);
480 }
481
482
483 static void cql_pr_int(int val,
484                        void (*pr)(const char *buf, void *client_data),
485                        void *client_data)
486 {
487     char buf[21];              /* enough characters to 2^64 */
488     sprintf(buf, "%d", val);
489     (*pr)(buf, client_data);
490     (*pr)(" ", client_data);
491 }
492
493
494 static int cql_pr_prox(cql_transform_t ct, struct cql_node *mods,
495                        WRBUF addinfo,
496                        void (*pr)(const char *buf, void *client_data),
497                        void *client_data)
498 {
499     int exclusion = 0;
500     int distance = -1;
501     int ordered = 0;
502     int proxrel = 2;            /* less than or equal */
503     int unit = 2;               /* word */
504
505     while (mods)
506     {
507         const char *name = mods->u.st.index;
508         const char *term = mods->u.st.term;
509         const char *relation = mods->u.st.relation;
510
511         if (!strcmp(name, "distance")) {
512             distance = strtol(term, (char**) 0, 0);
513             if (!strcmp(relation, "="))
514                 proxrel = 3;
515             else if (!strcmp(relation, ">"))
516                 proxrel = 5;
517             else if (!strcmp(relation, "<"))
518                 proxrel = 1;
519             else if (!strcmp(relation, ">="))
520                 proxrel = 4;
521             else if (!strcmp(relation, "<="))
522                 proxrel = 2;
523             else if (!strcmp(relation, "<>"))
524                 proxrel = 6;
525             else
526             {
527                 wrbuf_puts(addinfo, relation);
528                 return YAZ_SRW_UNSUPP_PROX_RELATION;
529             }
530         }
531         else if (!strcmp(name, "ordered"))
532             ordered = 1;
533         else if (!strcmp(name, "unordered"))
534             ordered = 0;
535         else if (!strcmp(name, "unit"))
536         {
537             if (!strcmp(term, "word"))
538                 unit = 2;
539             else if (!strcmp(term, "sentence"))
540                 unit = 3;
541             else if (!strcmp(term, "paragraph"))
542                 unit = 4;
543             else if (!strcmp(term, "element"))
544                 unit = 8;
545             else
546             {
547                 wrbuf_puts(addinfo, term);
548                 return YAZ_SRW_UNSUPP_PROX_UNIT;
549             }
550         }
551         else
552         {
553             wrbuf_puts(addinfo, name);
554             return YAZ_SRW_UNSUPP_BOOLEAN_MODIFIER;
555         }
556         mods = mods->u.st.modifiers;
557     }
558
559     if (distance == -1)
560         distance = (unit == 2) ? 1 : 0;
561
562     cql_pr_int(exclusion, pr, client_data);
563     cql_pr_int(distance, pr, client_data);
564     cql_pr_int(ordered, pr, client_data);
565     cql_pr_int(proxrel, pr, client_data);
566     (*pr)("k ", client_data);
567     cql_pr_int(unit, pr, client_data);
568
569     return 0;
570 }
571
572 /* ### checks for CQL relation-name rather than Type-1 attribute */
573 static int has_modifier(struct cql_node *cn, const char *name) {
574     struct cql_node *mod;
575     for (mod = cn->u.st.modifiers; mod != 0; mod = mod->u.st.modifiers) {
576         if (!strcmp(mod->u.st.index, name))
577             return 1;
578     }
579
580     return 0;
581 }
582
583 static int emit_term(cql_transform_t ct,
584                      struct cql_node *cn, WRBUF addinfo,
585                      const char *term, int length,
586                      void (*pr)(const char *buf, void *client_data),
587                      void *client_data)
588 {
589     int i, r;
590     const char *ns = cn->u.st.index_uri;
591     int z3958_mode = 0;
592     int process_term = 1;
593
594     if (has_modifier(cn, "regexp"))
595         process_term = 0;
596     else if (has_modifier(cn, "unmasked"))
597         process_term = 0;
598     else if (cql_lookup_property(ct, "truncation", 0, "cql"))
599     {
600         process_term = 0;
601         r = cql_pr_attr(ct, addinfo, "truncation", "cql", 0,
602                         pr, client_data, YAZ_SRW_MASKING_CHAR_UNSUPP);
603         if (r)
604             return r;
605     }
606     assert(cn->which == CQL_NODE_ST);
607
608     if (process_term)
609     {   /* convert term via truncation.things */
610         unsigned anchor = 0;
611         unsigned trunc = 0;
612         for (i = 0; i < length; i++)
613         {
614             if (term[i] == '\\' && i < length - 1)
615                 i++;
616             else
617             {
618                 switch (term[i])
619                 {
620                 case '^':
621                     if (i == 0)
622                         anchor |= 1;
623                     else if (i == length - 1)
624                         anchor |= 2;
625                     break;
626                 case '*':
627                     if (i == 0)
628                         trunc |= 1;
629                     else if (i == length - 1)
630                         trunc |= 2;
631                     else
632                         z3958_mode = 1;
633                     break;
634                 case '?':
635                     z3958_mode = 1;
636                     break;
637                 }
638             }
639         }
640         if (anchor == 3)
641         {
642             r = cql_pr_attr(ct, addinfo, "position", "firstAndLast", 0,
643                             pr, client_data,
644                             YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
645             if (r)
646                 return r;
647             term++;
648             length -= 2;
649         }
650         else if (anchor == 1)
651         {
652             r = cql_pr_attr(ct, addinfo, "position", "first", 0,
653                             pr, client_data,
654                             YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
655             if (r)
656                 return r;
657             term++;
658             length--;
659         }
660         else if (anchor == 2)
661         {
662             r = cql_pr_attr(ct, addinfo, "position", "last", 0,
663                             pr, client_data,
664                             YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
665             if (r)
666                 return r;
667             length--;
668         }
669         else
670         {
671             r = cql_pr_attr(ct, addinfo, "position", "any", 0,
672                         pr, client_data,
673                             YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
674             if (r)
675                 return r;
676         }
677         if (z3958_mode == 0)
678         {
679             if (trunc == 3 && !cql_pr_attr(ct, addinfo, "truncation",
680                                           "both", 0, pr, client_data, 0))
681             {
682                 term++;
683                 length -= 2;
684             }
685             else if (trunc == 1 && !cql_pr_attr(ct, addinfo, "truncation",
686                                                "left", 0, pr, client_data, 0))
687             {
688                 term++;
689                 length--;
690             }
691             else if (trunc == 2 && !cql_pr_attr(ct, addinfo, "truncation",
692                                                 "right", 0, pr, client_data, 0))
693             {
694                 length--;
695             }
696             else if (trunc)
697                 z3958_mode = 1;
698             else
699                 cql_pr_attr(ct, addinfo, "truncation", "none", 0,
700                             pr, client_data, 0);
701         }
702         if (z3958_mode)
703         {
704             r = cql_pr_attr(ct, addinfo, "truncation", "z3958", 0,
705                             pr, client_data, YAZ_SRW_MASKING_CHAR_UNSUPP);
706             if (r)
707                 return r;
708         }
709     }
710     if (ns)
711     {
712         r = cql_pr_attr_uri(ct, addinfo, "index", ns,
713                             cn->u.st.index, "serverChoice",
714                             pr, client_data, YAZ_SRW_UNSUPP_INDEX);
715         if (r)
716             return r;
717     }
718     if (cn->u.st.modifiers)
719     {
720         struct cql_node *mod = cn->u.st.modifiers;
721         for (; mod; mod = mod->u.st.modifiers)
722         {
723             r = cql_pr_attr(ct, addinfo,
724                             "relationModifier", mod->u.st.index, 0,
725                             pr, client_data, YAZ_SRW_UNSUPP_RELATION_MODIFIER);
726             if (r)
727                 return r;
728         }
729     }
730     (*pr)("\"", client_data);
731     if (process_term)
732         for (i = 0; i < length; i++)
733         {
734             char x[2]; /* temp buffer */
735             if (term[i] == '\\' && i < length - 1)
736             {
737                 i++;
738                 if (strchr("\"\\", term[i]))
739                     pr("\\", client_data);
740                 if (z3958_mode && strchr("#?", term[i]))
741                     pr("\\\\", client_data); /* double \\ to survive PQF parse */
742                 x[0] = term[i];
743                 x[1] = '\0';
744                 pr(x, client_data);
745             }
746             else if (z3958_mode && term[i] == '*')
747             {
748                 pr("?", client_data);
749                 if (i < length - 1 && yaz_isdigit(term[i+1]))
750                     pr("\\\\", client_data); /* dbl \\ to survive PQF parse */
751             }
752             else if (z3958_mode && term[i] == '?')
753             {
754                 pr("#", client_data);
755             }
756             else
757             {
758                 if (term[i] == '\"')
759                     pr("\\", client_data);
760                 if (z3958_mode && strchr("#?", term[i]))
761                     pr("\\\\", client_data); /* dbl \\ to survive PQF parse */
762                 x[0] = term[i];
763                 x[1] = '\0';
764                 pr(x, client_data);
765             }
766         }
767     else
768     {
769         for (i = 0; i < length; i++)
770         {
771             char x[2];
772             x[0] = term[i];
773             x[1] = '\0';
774             pr(x, client_data);
775         }
776     }
777     (*pr)("\" ", client_data);
778     return 0;
779 }
780
781 static int emit_terms(cql_transform_t ct, struct cql_node *cn,
782                       WRBUF addinfo,
783                       void (*pr)(const char *buf, void *client_data),
784                       void *client_data,
785                       const char *op)
786 {
787     struct cql_node *ne = cn->u.st.extra_terms;
788     int r;
789     if (ne)
790     {
791         (*pr)("@", client_data);
792         (*pr)(op, client_data);
793         (*pr)(" ", client_data);
794     }
795     r = emit_term(ct, cn, addinfo, cn->u.st.term, strlen(cn->u.st.term),
796                   pr, client_data);
797     for (; !r && ne; ne = ne->u.st.extra_terms)
798     {
799         if (ne->u.st.extra_terms)
800         {
801             (*pr)("@", client_data);
802             (*pr)(op, client_data);
803             (*pr)(" ", client_data);
804         }
805         r = emit_term(ct, cn, addinfo, ne->u.st.term, strlen(ne->u.st.term),
806                       pr, client_data);
807     }
808     return r;
809 }
810
811 static int emit_wordlist(cql_transform_t ct, struct cql_node *cn,
812                          WRBUF addinfo,
813                          void (*pr)(const char *buf, void *client_data),
814                          void *client_data,
815                          const char *op)
816 {
817     int r = 0;
818     const char *cp0 = cn->u.st.term;
819     const char *cp1;
820     const char *last_term = 0;
821     int last_length = 0;
822     while (!r && cp0)
823     {
824         while (*cp0 == ' ')
825             cp0++;
826         cp1 = strchr(cp0, ' ');
827         if (last_term)
828         {
829             (*pr)("@", client_data);
830             (*pr)(op, client_data);
831             (*pr)(" ", client_data);
832             r = emit_term(ct, cn, addinfo, last_term, last_length,
833                           pr, client_data);
834         }
835         last_term = cp0;
836         if (cp1)
837             last_length = cp1 - cp0;
838         else
839             last_length = strlen(cp0);
840         cp0 = cp1;
841     }
842     if (!r && last_term)
843         r = emit_term(ct, cn, addinfo, last_term, last_length, pr, client_data);
844     return r;
845 }
846
847 static int emit_node(cql_transform_t ct, struct cql_node *cn,
848                      WRBUF addinfo,
849                      void (*pr)(const char *buf, void *client_data),
850                      void *client_data)
851 {
852     const char *ns;
853     int r = 0;
854     struct cql_node *mods;
855
856     if (!cn)
857         return 0;
858     switch (cn->which)
859     {
860     case CQL_NODE_ST:
861         ns = cn->u.st.index_uri;
862         if (ns)
863         {
864             if (!strcmp(ns, cql_uri())
865                 && cn->u.st.index && !cql_strcmp(cn->u.st.index, "resultSet"))
866             {
867                 (*pr)("@set \"", client_data);
868                 (*pr)(cn->u.st.term, client_data);
869                 (*pr)("\" ", client_data);
870                 return 0;
871             }
872         }
873         else
874         {
875             return YAZ_SRW_UNSUPP_CONTEXT_SET;
876         }
877         cql_pr_attr(ct, addinfo, "always", 0, 0, pr, client_data, 0);
878         r = cql_pr_attr(ct, addinfo, "relation", cn->u.st.relation, 0,
879                         pr, client_data, YAZ_SRW_UNSUPP_RELATION);
880         if (r)
881             return r;
882         r = cql_pr_attr(ct, addinfo, "structure", cn->u.st.relation, 0,
883                         pr, client_data,
884                         YAZ_SRW_UNSUPP_COMBI_OF_RELATION_AND_TERM);
885         if (r)
886             return r;
887         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "all"))
888             r = emit_wordlist(ct, cn, addinfo, pr, client_data, "and");
889         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "any"))
890             r = emit_wordlist(ct, cn, addinfo, pr, client_data, "or");
891         else
892             r = emit_terms(ct, cn, addinfo, pr, client_data, "and");
893         break;
894     case CQL_NODE_BOOL:
895         (*pr)("@", client_data);
896         (*pr)(cn->u.boolean.value, client_data);
897         (*pr)(" ", client_data);
898         mods = cn->u.boolean.modifiers;
899         if (!strcmp(cn->u.boolean.value, "prox"))
900         {
901             r = cql_pr_prox(ct, mods, addinfo, pr, client_data);
902             if (r)
903                 return r;
904         }
905         else if (mods)
906         {
907             /* Boolean modifiers other than on proximity not supported */
908             wrbuf_puts(addinfo, mods->u.st.index);
909             return YAZ_SRW_UNSUPP_BOOLEAN_MODIFIER;
910         }
911
912         r = emit_node(ct, cn->u.boolean.left, addinfo, pr, client_data);
913         if (r)
914             return r;
915         r = emit_node(ct, cn->u.boolean.right, addinfo, pr, client_data);
916         if (r)
917             return r;
918         break;
919     case CQL_NODE_SORT:
920         r = emit_node(ct, cn->u.sort.search, addinfo, pr, client_data);
921         break;
922     default:
923         fprintf(stderr, "Fatal: impossible CQL node-type %d\n", cn->which);
924         abort();
925     }
926     return r;
927 }
928
929 int cql_transform_r(cql_transform_t ct, struct cql_node *cn,
930                     WRBUF addinfo,
931                     void (*pr)(const char *buf, void *client_data),
932                     void *client_data)
933 {
934     struct cql_prop_entry *e;
935     NMEM nmem = nmem_create();
936     int r;
937
938     for (e = ct->entry; e ; e = e->next)
939     {
940         if (!cql_strncmp(e->pattern, "set.", 4))
941             cql_apply_prefix(nmem, cn, e->pattern+4, e->value);
942         else if (!cql_strcmp(e->pattern, "set"))
943             cql_apply_prefix(nmem, cn, 0, e->value);
944     }
945     r  = emit_node(ct, cn, addinfo, pr, client_data);
946     nmem_destroy(nmem);
947     return r;
948 }
949
950 int cql_transform(cql_transform_t ct, struct cql_node *cn,
951                   void (*pr)(const char *buf, void *client_data),
952                   void *client_data)
953 {
954     WRBUF addinfo = wrbuf_alloc();
955     int r = cql_transform_r(ct, cn, addinfo, pr, client_data);
956     cql_transform_set_error(ct, r, wrbuf_cstr(addinfo));
957     wrbuf_destroy(addinfo);
958     return r;
959 }
960
961 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
962 {
963     return cql_transform(ct, cn, cql_fputs, f);
964 }
965
966 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
967                       char *out, int max)
968 {
969     struct cql_buf_write_info info;
970     int r;
971
972     info.off = 0;
973     info.max = max;
974     info.buf = out;
975     r = cql_transform(ct, cn, cql_buf_write_handler, &info);
976     if (info.off < 0) {
977         /* Attempt to write past end of buffer.  For some reason, this
978            SRW diagnostic is deprecated, but it's so perfect for our
979            purposes that it would be stupid not to use it. */
980         char numbuf[30];
981         sprintf(numbuf, "%ld", (long) info.max);
982         cql_transform_set_error(ct, YAZ_SRW_TOO_MANY_CHARS_IN_QUERY, numbuf);
983         return -1;
984     }
985     if (info.off >= 0)
986         info.buf[info.off] = '\0';
987     return r;
988 }
989
990 int cql_transform_error(cql_transform_t ct, const char **addinfo)
991 {
992     *addinfo = wrbuf_len(ct->addinfo) ? wrbuf_cstr(ct->addinfo) : 0;
993     return ct->error;
994 }
995
996 void cql_transform_set_error(cql_transform_t ct, int error, const char *addinfo)
997 {
998     wrbuf_rewind(ct->addinfo);
999     if (addinfo)
1000         wrbuf_puts(ct->addinfo, addinfo);
1001     ct->error = error;
1002 }
1003
1004 /*
1005  * Local variables:
1006  * c-basic-offset: 4
1007  * c-file-style: "Stroustrup"
1008  * indent-tabs-mode: nil
1009  * End:
1010  * vim: shiftwidth=4 tabstop=8 expandtab
1011  */
1012