f560445d9708a4aa33a85d3e7a1d7451364385a8
[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, char **addinfo, 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 0;
462     }
463     /* error ... */
464     if (errcode == 0)
465         return 1; /* signal error, but do not set addinfo */
466     if (val)
467         *addinfo = xstrdup(val);
468     return errcode;
469 }
470
471 int cql_pr_attr(cql_transform_t ct, char **addinfo, const char *category,
472                 const char *val, const char *default_val,
473                 void (*pr)(const char *buf, void *client_data),
474                 void *client_data,
475                 int errcode)
476 {
477     return cql_pr_attr_uri(ct, addinfo, category, 0 /* uri */,
478                            val, default_val, pr, client_data, errcode);
479 }
480
481
482 static void cql_pr_int(int val,
483                        void (*pr)(const char *buf, void *client_data),
484                        void *client_data)
485 {
486     char buf[21];              /* enough characters to 2^64 */
487     sprintf(buf, "%d", val);
488     (*pr)(buf, client_data);
489     (*pr)(" ", client_data);
490 }
491
492
493 static int cql_pr_prox(cql_transform_t ct, struct cql_node *mods,
494                        char **addinfo,
495                        void (*pr)(const char *buf, void *client_data),
496                        void *client_data)
497 {
498     int exclusion = 0;
499     int distance = -1;
500     int ordered = 0;
501     int proxrel = 2;            /* less than or equal */
502     int unit = 2;               /* word */
503
504     while (mods)
505     {
506         const char *name = mods->u.st.index;
507         const char *term = mods->u.st.term;
508         const char *relation = mods->u.st.relation;
509
510         if (!strcmp(name, "distance")) {
511             distance = strtol(term, (char**) 0, 0);
512             if (!strcmp(relation, "="))
513                 proxrel = 3;
514             else if (!strcmp(relation, ">"))
515                 proxrel = 5;
516             else if (!strcmp(relation, "<"))
517                 proxrel = 1;
518             else if (!strcmp(relation, ">="))
519                 proxrel = 4;
520             else if (!strcmp(relation, "<="))
521                 proxrel = 2;
522             else if (!strcmp(relation, "<>"))
523                 proxrel = 6;
524             else
525             {
526                 *addinfo = xstrdup(relation);
527                 return YAZ_SRW_UNSUPP_PROX_RELATION;
528             }
529         }
530         else if (!strcmp(name, "ordered"))
531             ordered = 1;
532         else if (!strcmp(name, "unordered"))
533             ordered = 0;
534         else if (!strcmp(name, "unit"))
535         {
536             if (!strcmp(term, "word"))
537                 unit = 2;
538             else if (!strcmp(term, "sentence"))
539                 unit = 3;
540             else if (!strcmp(term, "paragraph"))
541                 unit = 4;
542             else if (!strcmp(term, "element"))
543                 unit = 8;
544             else
545             {
546                 *addinfo = xstrdup(term);
547                 return YAZ_SRW_UNSUPP_PROX_UNIT;
548             }
549         }
550         else
551         {
552             *addinfo = xstrdup(name);
553             return YAZ_SRW_UNSUPP_BOOLEAN_MODIFIER;
554         }
555         mods = mods->u.st.modifiers;
556     }
557
558     if (distance == -1)
559         distance = (unit == 2) ? 1 : 0;
560
561     cql_pr_int(exclusion, pr, client_data);
562     cql_pr_int(distance, pr, client_data);
563     cql_pr_int(ordered, pr, client_data);
564     cql_pr_int(proxrel, pr, client_data);
565     (*pr)("k ", client_data);
566     cql_pr_int(unit, pr, client_data);
567
568     return 0;
569 }
570
571 /* ### checks for CQL relation-name rather than Type-1 attribute */
572 static int has_modifier(struct cql_node *cn, const char *name) {
573     struct cql_node *mod;
574     for (mod = cn->u.st.modifiers; mod != 0; mod = mod->u.st.modifiers) {
575         if (!strcmp(mod->u.st.index, name))
576             return 1;
577     }
578
579     return 0;
580 }
581
582 static int emit_term(cql_transform_t ct,
583                      struct cql_node *cn, char **addinfo,
584                      const char *term, int length,
585                      void (*pr)(const char *buf, void *client_data),
586                      void *client_data)
587 {
588     int i, r;
589     const char *ns = cn->u.st.index_uri;
590     int z3958_mode = 0;
591     int process_term = 1;
592
593     if (has_modifier(cn, "regexp"))
594         process_term = 0;
595     else if (has_modifier(cn, "unmasked"))
596         process_term = 0;
597     else if (cql_lookup_property(ct, "truncation", 0, "cql"))
598     {
599         process_term = 0;
600         r = cql_pr_attr(ct, addinfo, "truncation", "cql", 0,
601                         pr, client_data, YAZ_SRW_MASKING_CHAR_UNSUPP);
602         if (r)
603             return r;
604     }
605     assert(cn->which == CQL_NODE_ST);
606
607     if (process_term)
608     {   /* convert term via truncation.things */
609         unsigned anchor = 0;
610         unsigned trunc = 0;
611         for (i = 0; i < length; i++)
612         {
613             if (term[i] == '\\' && i < length - 1)
614                 i++;
615             else
616             {
617                 switch (term[i])
618                 {
619                 case '^':
620                     if (i == 0)
621                         anchor |= 1;
622                     else if (i == length - 1)
623                         anchor |= 2;
624                     break;
625                 case '*':
626                     if (i == 0)
627                         trunc |= 1;
628                     else if (i == length - 1)
629                         trunc |= 2;
630                     else
631                         z3958_mode = 1;
632                     break;
633                 case '?':
634                     z3958_mode = 1;
635                     break;
636                 }
637             }
638         }
639         if (anchor == 3)
640         {
641             r = cql_pr_attr(ct, addinfo, "position", "firstAndLast", 0,
642                             pr, client_data,
643                             YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
644             if (r)
645                 return r;
646             term++;
647             length -= 2;
648         }
649         else if (anchor == 1)
650         {
651             r = cql_pr_attr(ct, addinfo, "position", "first", 0,
652                             pr, client_data,
653                             YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
654             if (r)
655                 return r;
656             term++;
657             length--;
658         }
659         else if (anchor == 2)
660         {
661             r = cql_pr_attr(ct, addinfo, "position", "last", 0,
662                             pr, client_data,
663                             YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
664             if (r)
665                 return r;
666             length--;
667         }
668         else
669         {
670             r = cql_pr_attr(ct, addinfo, "position", "any", 0,
671                         pr, client_data,
672                             YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
673             if (r)
674                 return r;
675         }
676         if (z3958_mode == 0)
677         {
678             if (trunc == 3 && !cql_pr_attr(ct, addinfo, "truncation",
679                                           "both", 0, pr, client_data, 0))
680             {
681                 term++;
682                 length -= 2;
683             }
684             else if (trunc == 1 && !cql_pr_attr(ct, addinfo, "truncation",
685                                                "left", 0, pr, client_data, 0))
686             {
687                 term++;
688                 length--;
689             }
690             else if (trunc == 2 && !cql_pr_attr(ct, addinfo, "truncation",
691                                                 "right", 0, pr, client_data, 0))
692             {
693                 length--;
694             }
695             else if (trunc)
696                 z3958_mode = 1;
697             else
698                 cql_pr_attr(ct, addinfo, "truncation", "none", 0,
699                             pr, client_data, 0);
700         }
701         if (z3958_mode)
702         {
703             r = cql_pr_attr(ct, addinfo, "truncation", "z3958", 0,
704                             pr, client_data, YAZ_SRW_MASKING_CHAR_UNSUPP);
705             if (r)
706                 return r;
707         }
708     }
709     if (ns)
710     {
711         r = cql_pr_attr_uri(ct, addinfo, "index", ns,
712                             cn->u.st.index, "serverChoice",
713                             pr, client_data, YAZ_SRW_UNSUPP_INDEX);
714         if (r)
715             return r;
716     }
717     if (cn->u.st.modifiers)
718     {
719         struct cql_node *mod = cn->u.st.modifiers;
720         for (; mod; mod = mod->u.st.modifiers)
721         {
722             r = cql_pr_attr(ct, addinfo,
723                             "relationModifier", mod->u.st.index, 0,
724                             pr, client_data, YAZ_SRW_UNSUPP_RELATION_MODIFIER);
725             if (r)
726                 return r;
727         }
728     }
729     (*pr)("\"", client_data);
730     if (process_term)
731         for (i = 0; i < length; i++)
732         {
733             char x[2]; /* temp buffer */
734             if (term[i] == '\\' && i < length - 1)
735             {
736                 i++;
737                 if (strchr("\"\\", term[i]))
738                     pr("\\", client_data);
739                 if (z3958_mode && strchr("#?", term[i]))
740                     pr("\\\\", client_data); /* double \\ to survive PQF parse */
741                 x[0] = term[i];
742                 x[1] = '\0';
743                 pr(x, client_data);
744             }
745             else if (z3958_mode && term[i] == '*')
746             {
747                 pr("?", client_data);
748                 if (i < length - 1 && yaz_isdigit(term[i+1]))
749                     pr("\\\\", client_data); /* dbl \\ to survive PQF parse */
750             }
751             else if (z3958_mode && term[i] == '?')
752             {
753                 pr("#", client_data);
754             }
755             else
756             {
757                 if (term[i] == '\"')
758                     pr("\\", client_data);
759                 if (z3958_mode && strchr("#?", term[i]))
760                     pr("\\\\", client_data); /* dbl \\ to survive PQF parse */
761                 x[0] = term[i];
762                 x[1] = '\0';
763                 pr(x, client_data);
764             }
765         }
766     else
767     {
768         for (i = 0; i < length; i++)
769         {
770             char x[2];
771             x[0] = term[i];
772             x[1] = '\0';
773             pr(x, client_data);
774         }
775     }
776     (*pr)("\" ", client_data);
777     return 0;
778 }
779
780 static int emit_terms(cql_transform_t ct, struct cql_node *cn,
781                       char **addinfo,
782                       void (*pr)(const char *buf, void *client_data),
783                       void *client_data,
784                       const char *op)
785 {
786     struct cql_node *ne = cn->u.st.extra_terms;
787     int r;
788     if (ne)
789     {
790         (*pr)("@", client_data);
791         (*pr)(op, client_data);
792         (*pr)(" ", client_data);
793     }
794     r = emit_term(ct, cn, addinfo, cn->u.st.term, strlen(cn->u.st.term),
795                   pr, client_data);
796     for (; !r && ne; ne = ne->u.st.extra_terms)
797     {
798         if (ne->u.st.extra_terms)
799         {
800             (*pr)("@", client_data);
801             (*pr)(op, client_data);
802             (*pr)(" ", client_data);
803         }
804         r = emit_term(ct, cn, addinfo, ne->u.st.term, strlen(ne->u.st.term),
805                       pr, client_data);
806     }
807     return r;
808 }
809
810 static int emit_wordlist(cql_transform_t ct, struct cql_node *cn,
811                          char **addinfo,
812                          void (*pr)(const char *buf, void *client_data),
813                          void *client_data,
814                          const char *op)
815 {
816     int r = 0;
817     const char *cp0 = cn->u.st.term;
818     const char *cp1;
819     const char *last_term = 0;
820     int last_length = 0;
821     while (!r && cp0)
822     {
823         while (*cp0 == ' ')
824             cp0++;
825         cp1 = strchr(cp0, ' ');
826         if (last_term)
827         {
828             (*pr)("@", client_data);
829             (*pr)(op, client_data);
830             (*pr)(" ", client_data);
831             r = emit_term(ct, cn, addinfo, last_term, last_length,
832                           pr, client_data);
833         }
834         last_term = cp0;
835         if (cp1)
836             last_length = cp1 - cp0;
837         else
838             last_length = strlen(cp0);
839         cp0 = cp1;
840     }
841     if (!r && last_term)
842         r = emit_term(ct, cn, addinfo, last_term, last_length, pr, client_data);
843     return r;
844 }
845
846 int cql_transform_r(cql_transform_t ct, struct cql_node *cn,
847                     char **addinfo,
848                     void (*pr)(const char *buf, void *client_data),
849                     void *client_data)
850 {
851     const char *ns;
852     int r = 0;
853     struct cql_node *mods;
854
855     if (!cn)
856         return 0;
857     switch (cn->which)
858     {
859     case CQL_NODE_ST:
860         ns = cn->u.st.index_uri;
861         if (ns)
862         {
863             if (!strcmp(ns, cql_uri())
864                 && cn->u.st.index && !cql_strcmp(cn->u.st.index, "resultSet"))
865             {
866                 (*pr)("@set \"", client_data);
867                 (*pr)(cn->u.st.term, client_data);
868                 (*pr)("\" ", client_data);
869                 return 0;
870             }
871         }
872         else
873         {
874             *addinfo = 0;
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             *addinfo = xstrdup(mods->u.st.index);
909             return YAZ_SRW_UNSUPP_BOOLEAN_MODIFIER;
910         }
911
912         r = cql_transform_r(ct, cn->u.boolean.left, addinfo, pr, client_data);
913         if (r)
914             return r;
915         r = cql_transform_r(ct, cn->u.boolean.right, addinfo, pr, client_data);
916         if (r)
917             return r;
918         break;
919     case CQL_NODE_SORT:
920         r = cql_transform_r(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_cql2rpn(cql_transform_t ct, struct cql_node *cn,
930                           char **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  = cql_transform_r(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     char *addinfo = 0;
955     int r = cql_transform_cql2rpn(ct, cn, &addinfo, pr, client_data);
956     cql_transform_set_error(ct, r, addinfo);
957     xfree(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 = ct->addinfo;
993     return ct->error;
994 }
995
996 void cql_transform_set_error(cql_transform_t ct, int error, const char *addinfo)
997 {
998     xfree(ct->addinfo);
999     ct->addinfo = addinfo ? xstrdup(addinfo) : 0;
1000     ct->error = error;
1001 }
1002
1003 /*
1004  * Local variables:
1005  * c-basic-offset: 4
1006  * c-file-style: "Stroustrup"
1007  * indent-tabs-mode: nil
1008  * End:
1009  * vim: shiftwidth=4 tabstop=8 expandtab
1010  */
1011