Extend get_org_info (snippets) to return original string YAZ-836
[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                     int r = compare_attr(e_ae, a_ae);
344                     if (r == 0)
345                         break;
346                 }
347                 if (j == attributes->num_attributes)
348                     break; /* i was not found at all.. try next pattern */
349
350             }
351             if (i == e->attr_list.num_attributes)
352                 return e->pattern + clen;
353         }
354     }
355     return 0;
356 }
357
358 static const char *cql_lookup_property(cql_transform_t ct,
359                                        const char *pat1, const char *pat2,
360                                        const char *pat3)
361 {
362     char pattern[120];
363     struct cql_prop_entry *e;
364
365     if (pat1 && pat2 && pat3)
366         sprintf(pattern, "%.39s.%.39s.%.39s", pat1, pat2, pat3);
367     else if (pat1 && pat2)
368         sprintf(pattern, "%.39s.%.39s", pat1, pat2);
369     else if (pat1 && pat3)
370         sprintf(pattern, "%.39s.%.39s", pat1, pat3);
371     else if (pat1)
372         sprintf(pattern, "%.39s", pat1);
373     else
374         return 0;
375
376     for (e = ct->entry; e; e = e->next)
377     {
378         if (!cql_strcmp(e->pattern, pattern))
379             return e->value;
380     }
381     return 0;
382 }
383
384 int cql_pr_attr_uri(cql_transform_t ct, WRBUF addinfo, const char *category,
385                    const char *uri, const char *val, const char *default_val,
386                    void (*pr)(const char *buf, void *client_data),
387                    void *client_data,
388                    int errcode)
389 {
390     const char *res = 0;
391     const char *eval = val ? val : default_val;
392     const char *prefix = 0;
393
394     if (uri)
395     {
396         struct cql_prop_entry *e;
397
398         for (e = ct->entry; e; e = e->next)
399             if (!memcmp(e->pattern, "set.", 4) && e->value &&
400                 !strcmp(e->value, uri))
401             {
402                 prefix = e->pattern+4;
403                 break;
404             }
405         /* must have a prefix now - if not it's an error */
406     }
407
408     if (!uri || prefix)
409     {
410         if (!res)
411             res = cql_lookup_property(ct, category, prefix, eval);
412         /* we have some aliases for some relations unfortunately.. */
413         if (!res && !prefix && !strcmp(category, "relation"))
414         {
415             if (!strcmp(val, "=="))
416                 res = cql_lookup_property(ct, category, prefix, "exact");
417             if (!strcmp(val, "="))
418                 res = cql_lookup_property(ct, category, prefix, "eq");
419             if (!strcmp(val, "<="))
420                 res = cql_lookup_property(ct, category, prefix, "le");
421             if (!strcmp(val, ">="))
422                 res = cql_lookup_property(ct, category, prefix, "ge");
423         }
424         if (!res)
425             res = cql_lookup_property(ct, category, prefix, "*");
426     }
427     if (res)
428     {
429         char buf[64];
430
431         const char *cp0 = res, *cp1;
432         while ((cp1 = strchr(cp0, '=')))
433         {
434             int i;
435             while (*cp1 && *cp1 != ' ')
436                 cp1++;
437             if (cp1 - cp0 >= (ptrdiff_t) sizeof(buf))
438                 break;
439             memcpy(buf, cp0, cp1 - cp0);
440             buf[cp1-cp0] = 0;
441             (*pr)("@attr ", client_data);
442
443             for (i = 0; buf[i]; i++)
444             {
445                 if (buf[i] == '*')
446                     (*pr)(eval, client_data);
447                 else
448                 {
449                     char tmp[2];
450                     tmp[0] = buf[i];
451                     tmp[1] = '\0';
452                     (*pr)(tmp, client_data);
453                 }
454             }
455             (*pr)(" ", client_data);
456             cp0 = cp1;
457             while (*cp0 == ' ')
458                 cp0++;
459         }
460         return 0;
461     }
462     /* error ... */
463     if (errcode == 0)
464         return 1; /* signal error, but do not set addinfo */
465     if (val)
466         wrbuf_puts(addinfo, val);
467     return errcode;
468 }
469
470 int cql_pr_attr(cql_transform_t ct, WRBUF addinfo, const char *category,
471                 const char *val, const char *default_val,
472                 void (*pr)(const char *buf, void *client_data),
473                 void *client_data,
474                 int errcode)
475 {
476     return cql_pr_attr_uri(ct, addinfo, category, 0 /* uri */,
477                            val, default_val, pr, client_data, errcode);
478 }
479
480
481 static void cql_pr_int(int val,
482                        void (*pr)(const char *buf, void *client_data),
483                        void *client_data)
484 {
485     char buf[21];              /* enough characters to 2^64 */
486     sprintf(buf, "%d", val);
487     (*pr)(buf, client_data);
488     (*pr)(" ", client_data);
489 }
490
491
492 static int cql_pr_prox(cql_transform_t ct, struct cql_node *mods,
493                        WRBUF addinfo,
494                        void (*pr)(const char *buf, void *client_data),
495                        void *client_data)
496 {
497     int exclusion = 0;
498     int distance = -1;
499     int ordered = 0;
500     int proxrel = 2;            /* less than or equal */
501     int unit = 2;               /* word */
502
503     while (mods)
504     {
505         const char *name = mods->u.st.index;
506         const char *term = mods->u.st.term;
507         const char *relation = mods->u.st.relation;
508
509         if (!strcmp(name, "distance")) {
510             distance = strtol(term, (char**) 0, 0);
511             if (!strcmp(relation, "="))
512                 proxrel = 3;
513             else if (!strcmp(relation, ">"))
514                 proxrel = 5;
515             else if (!strcmp(relation, "<"))
516                 proxrel = 1;
517             else if (!strcmp(relation, ">="))
518                 proxrel = 4;
519             else if (!strcmp(relation, "<="))
520                 proxrel = 2;
521             else if (!strcmp(relation, "<>"))
522                 proxrel = 6;
523             else
524             {
525                 wrbuf_puts(addinfo, relation);
526                 return YAZ_SRW_UNSUPP_PROX_RELATION;
527             }
528         }
529         else if (!strcmp(name, "ordered"))
530             ordered = 1;
531         else if (!strcmp(name, "unordered"))
532             ordered = 0;
533         else if (!strcmp(name, "unit"))
534         {
535             if (!strcmp(term, "word"))
536                 unit = 2;
537             else if (!strcmp(term, "sentence"))
538                 unit = 3;
539             else if (!strcmp(term, "paragraph"))
540                 unit = 4;
541             else if (!strcmp(term, "element"))
542                 unit = 8;
543             else
544             {
545                 wrbuf_puts(addinfo, term);
546                 return YAZ_SRW_UNSUPP_PROX_UNIT;
547             }
548         }
549         else
550         {
551             wrbuf_puts(addinfo, name);
552             return YAZ_SRW_UNSUPP_BOOLEAN_MODIFIER;
553         }
554         mods = mods->u.st.modifiers;
555     }
556
557     if (distance == -1)
558         distance = (unit == 2) ? 1 : 0;
559
560     cql_pr_int(exclusion, pr, client_data);
561     cql_pr_int(distance, pr, client_data);
562     cql_pr_int(ordered, pr, client_data);
563     cql_pr_int(proxrel, pr, client_data);
564     (*pr)("k ", client_data);
565     cql_pr_int(unit, pr, client_data);
566
567     return 0;
568 }
569
570 /* ### checks for CQL relation-name rather than Type-1 attribute */
571 static int has_modifier(struct cql_node *cn, const char *name) {
572     struct cql_node *mod;
573     for (mod = cn->u.st.modifiers; mod != 0; mod = mod->u.st.modifiers) {
574         if (!strcmp(mod->u.st.index, name))
575             return 1;
576     }
577
578     return 0;
579 }
580
581 static int emit_term(cql_transform_t ct,
582                      struct cql_node *cn, WRBUF addinfo,
583                      const char *term, int length,
584                      void (*pr)(const char *buf, void *client_data),
585                      void *client_data)
586 {
587     int i, r;
588     const char *ns = cn->u.st.index_uri;
589     int z3958_mode = 0;
590     int process_term = 1;
591
592     if (has_modifier(cn, "regexp"))
593         process_term = 0;
594     else if (has_modifier(cn, "unmasked"))
595         process_term = 0;
596     else if (cql_lookup_property(ct, "truncation", 0, "cql"))
597     {
598         process_term = 0;
599         r = cql_pr_attr(ct, addinfo, "truncation", "cql", 0,
600                         pr, client_data, YAZ_SRW_MASKING_CHAR_UNSUPP);
601         if (r)
602             return r;
603     }
604     assert(cn->which == CQL_NODE_ST);
605
606     if (process_term)
607     {   /* convert term via truncation.things */
608         unsigned anchor = 0;
609         unsigned trunc = 0;
610         for (i = 0; i < length; i++)
611         {
612             if (term[i] == '\\' && i < length - 1)
613                 i++;
614             else
615             {
616                 switch (term[i])
617                 {
618                 case '^':
619                     if (i == 0)
620                         anchor |= 1;
621                     else if (i == length - 1)
622                         anchor |= 2;
623                     break;
624                 case '*':
625                     if (i == 0)
626                         trunc |= 1;
627                     else if (i == length - 1)
628                         trunc |= 2;
629                     else
630                         z3958_mode = 1;
631                     break;
632                 case '?':
633                     z3958_mode = 1;
634                     break;
635                 }
636             }
637         }
638         if (anchor == 3)
639         {
640             r = cql_pr_attr(ct, addinfo, "position", "firstAndLast", 0,
641                             pr, client_data,
642                             YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
643             if (r)
644                 return r;
645             term++;
646             length -= 2;
647         }
648         else if (anchor == 1)
649         {
650             r = cql_pr_attr(ct, addinfo, "position", "first", 0,
651                             pr, client_data,
652                             YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
653             if (r)
654                 return r;
655             term++;
656             length--;
657         }
658         else if (anchor == 2)
659         {
660             r = cql_pr_attr(ct, addinfo, "position", "last", 0,
661                             pr, client_data,
662                             YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
663             if (r)
664                 return r;
665             length--;
666         }
667         else
668         {
669             r = cql_pr_attr(ct, addinfo, "position", "any", 0,
670                         pr, client_data,
671                             YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
672             if (r)
673                 return r;
674         }
675         if (z3958_mode == 0)
676         {
677             if (trunc == 3 && !cql_pr_attr(ct, addinfo, "truncation",
678                                           "both", 0, pr, client_data, 0))
679             {
680                 term++;
681                 length -= 2;
682             }
683             else if (trunc == 1 && !cql_pr_attr(ct, addinfo, "truncation",
684                                                "left", 0, pr, client_data, 0))
685             {
686                 term++;
687                 length--;
688             }
689             else if (trunc == 2 && !cql_pr_attr(ct, addinfo, "truncation",
690                                                 "right", 0, pr, client_data, 0))
691             {
692                 length--;
693             }
694             else if (trunc)
695                 z3958_mode = 1;
696             else
697                 cql_pr_attr(ct, addinfo, "truncation", "none", 0,
698                             pr, client_data, 0);
699         }
700         if (z3958_mode)
701         {
702             r = cql_pr_attr(ct, addinfo, "truncation", "z3958", 0,
703                             pr, client_data, YAZ_SRW_MASKING_CHAR_UNSUPP);
704             if (r)
705                 return r;
706         }
707     }
708     if (ns)
709     {
710         r = cql_pr_attr_uri(ct, addinfo, "index", ns,
711                             cn->u.st.index, "serverChoice",
712                             pr, client_data, YAZ_SRW_UNSUPP_INDEX);
713         if (r)
714             return r;
715     }
716     if (cn->u.st.modifiers)
717     {
718         struct cql_node *mod = cn->u.st.modifiers;
719         for (; mod; mod = mod->u.st.modifiers)
720         {
721             r = cql_pr_attr(ct, addinfo,
722                             "relationModifier", mod->u.st.index, 0,
723                             pr, client_data, YAZ_SRW_UNSUPP_RELATION_MODIFIER);
724             if (r)
725                 return r;
726         }
727     }
728     (*pr)("\"", client_data);
729     if (process_term)
730         for (i = 0; i < length; i++)
731         {
732             char x[2]; /* temp buffer */
733             if (term[i] == '\\' && i < length - 1)
734             {
735                 i++;
736                 if (strchr("\"\\", term[i]))
737                     pr("\\", client_data);
738                 if (z3958_mode && strchr("#?", term[i]))
739                     pr("\\\\", client_data); /* double \\ to survive PQF parse */
740                 x[0] = term[i];
741                 x[1] = '\0';
742                 pr(x, client_data);
743             }
744             else if (z3958_mode && term[i] == '*')
745             {
746                 pr("?", client_data);
747                 if (i < length - 1 && yaz_isdigit(term[i+1]))
748                     pr("\\\\", client_data); /* dbl \\ to survive PQF parse */
749             }
750             else if (z3958_mode && term[i] == '?')
751             {
752                 pr("#", client_data);
753             }
754             else
755             {
756                 if (term[i] == '\"')
757                     pr("\\", client_data);
758                 if (z3958_mode && strchr("#?", term[i]))
759                     pr("\\\\", client_data); /* dbl \\ to survive PQF parse */
760                 x[0] = term[i];
761                 x[1] = '\0';
762                 pr(x, client_data);
763             }
764         }
765     else
766     {
767         for (i = 0; i < length; i++)
768         {
769             char x[2];
770             x[0] = term[i];
771             x[1] = '\0';
772             pr(x, client_data);
773         }
774     }
775     (*pr)("\" ", client_data);
776     return 0;
777 }
778
779 static int emit_terms(cql_transform_t ct, struct cql_node *cn,
780                       WRBUF addinfo,
781                       void (*pr)(const char *buf, void *client_data),
782                       void *client_data,
783                       const char *op)
784 {
785     struct cql_node *ne = cn->u.st.extra_terms;
786     int r;
787     if (ne)
788     {
789         (*pr)("@", client_data);
790         (*pr)(op, client_data);
791         (*pr)(" ", client_data);
792     }
793     r = emit_term(ct, cn, addinfo, cn->u.st.term, strlen(cn->u.st.term),
794                   pr, client_data);
795     for (; !r && ne; ne = ne->u.st.extra_terms)
796     {
797         if (ne->u.st.extra_terms)
798         {
799             (*pr)("@", client_data);
800             (*pr)(op, client_data);
801             (*pr)(" ", client_data);
802         }
803         r = emit_term(ct, cn, addinfo, ne->u.st.term, strlen(ne->u.st.term),
804                       pr, client_data);
805     }
806     return r;
807 }
808
809 static int emit_wordlist(cql_transform_t ct, struct cql_node *cn,
810                          WRBUF addinfo,
811                          void (*pr)(const char *buf, void *client_data),
812                          void *client_data,
813                          const char *op)
814 {
815     int r = 0;
816     const char *cp0 = cn->u.st.term;
817     const char *cp1;
818     const char *last_term = 0;
819     int last_length = 0;
820     while (!r && cp0)
821     {
822         while (*cp0 == ' ')
823             cp0++;
824         cp1 = strchr(cp0, ' ');
825         if (last_term)
826         {
827             (*pr)("@", client_data);
828             (*pr)(op, client_data);
829             (*pr)(" ", client_data);
830             r = emit_term(ct, cn, addinfo, last_term, last_length,
831                           pr, client_data);
832         }
833         last_term = cp0;
834         if (cp1)
835             last_length = cp1 - cp0;
836         else
837             last_length = strlen(cp0);
838         cp0 = cp1;
839     }
840     if (!r && last_term)
841         r = emit_term(ct, cn, addinfo, last_term, last_length, pr, client_data);
842     return r;
843 }
844
845 static int emit_node(cql_transform_t ct, struct cql_node *cn,
846                      WRBUF addinfo,
847                      void (*pr)(const char *buf, void *client_data),
848                      void *client_data)
849 {
850     const char *ns;
851     int r = 0;
852     struct cql_node *mods;
853
854     if (!cn)
855         return 0;
856     switch (cn->which)
857     {
858     case CQL_NODE_ST:
859         ns = cn->u.st.index_uri;
860         if (ns)
861         {
862             if (!strcmp(ns, cql_uri())
863                 && cn->u.st.index && !cql_strcmp(cn->u.st.index, "resultSet"))
864             {
865                 (*pr)("@set \"", client_data);
866                 (*pr)(cn->u.st.term, client_data);
867                 (*pr)("\" ", client_data);
868                 return 0;
869             }
870         }
871         else
872         {
873             return YAZ_SRW_UNSUPP_CONTEXT_SET;
874         }
875         cql_pr_attr(ct, addinfo, "always", 0, 0, pr, client_data, 0);
876         r = cql_pr_attr(ct, addinfo, "relation", cn->u.st.relation, 0,
877                         pr, client_data, YAZ_SRW_UNSUPP_RELATION);
878         if (r)
879             return r;
880         r = cql_pr_attr(ct, addinfo, "structure", cn->u.st.relation, 0,
881                         pr, client_data,
882                         YAZ_SRW_UNSUPP_COMBI_OF_RELATION_AND_TERM);
883         if (r)
884             return r;
885         if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "all"))
886             r = emit_wordlist(ct, cn, addinfo, pr, client_data, "and");
887         else if (cn->u.st.relation && !cql_strcmp(cn->u.st.relation, "any"))
888             r = emit_wordlist(ct, cn, addinfo, pr, client_data, "or");
889         else
890             r = emit_terms(ct, cn, addinfo, pr, client_data, "and");
891         break;
892     case CQL_NODE_BOOL:
893         (*pr)("@", client_data);
894         (*pr)(cn->u.boolean.value, client_data);
895         (*pr)(" ", client_data);
896         mods = cn->u.boolean.modifiers;
897         if (!strcmp(cn->u.boolean.value, "prox"))
898         {
899             r = cql_pr_prox(ct, mods, addinfo, pr, client_data);
900             if (r)
901                 return r;
902         }
903         else if (mods)
904         {
905             /* Boolean modifiers other than on proximity not supported */
906             wrbuf_puts(addinfo, mods->u.st.index);
907             return YAZ_SRW_UNSUPP_BOOLEAN_MODIFIER;
908         }
909
910         r = emit_node(ct, cn->u.boolean.left, addinfo, pr, client_data);
911         if (r)
912             return r;
913         r = emit_node(ct, cn->u.boolean.right, addinfo, pr, client_data);
914         if (r)
915             return r;
916         break;
917     case CQL_NODE_SORT:
918         r = emit_node(ct, cn->u.sort.search, addinfo, pr, client_data);
919         break;
920     default:
921         fprintf(stderr, "Fatal: impossible CQL node-type %d\n", cn->which);
922         abort();
923     }
924     return r;
925 }
926
927 int cql_transform_r(cql_transform_t ct, struct cql_node *cn,
928                     WRBUF addinfo,
929                     void (*pr)(const char *buf, void *client_data),
930                     void *client_data)
931 {
932     struct cql_prop_entry *e;
933     NMEM nmem = nmem_create();
934     int r;
935
936     for (e = ct->entry; e ; e = e->next)
937     {
938         if (!cql_strncmp(e->pattern, "set.", 4))
939             cql_apply_prefix(nmem, cn, e->pattern+4, e->value);
940         else if (!cql_strcmp(e->pattern, "set"))
941             cql_apply_prefix(nmem, cn, 0, e->value);
942     }
943     r  = emit_node(ct, cn, addinfo, pr, client_data);
944     nmem_destroy(nmem);
945     return r;
946 }
947
948 int cql_transform(cql_transform_t ct, struct cql_node *cn,
949                   void (*pr)(const char *buf, void *client_data),
950                   void *client_data)
951 {
952     WRBUF addinfo = wrbuf_alloc();
953     int r = cql_transform_r(ct, cn, addinfo, pr, client_data);
954     cql_transform_set_error(ct, r, wrbuf_cstr(addinfo));
955     wrbuf_destroy(addinfo);
956     return r;
957 }
958
959 int cql_transform_FILE(cql_transform_t ct, struct cql_node *cn, FILE *f)
960 {
961     return cql_transform(ct, cn, cql_fputs, f);
962 }
963
964 int cql_transform_buf(cql_transform_t ct, struct cql_node *cn,
965                       char *out, int max)
966 {
967     struct cql_buf_write_info info;
968     int r;
969
970     info.off = 0;
971     info.max = max;
972     info.buf = out;
973     r = cql_transform(ct, cn, cql_buf_write_handler, &info);
974     if (info.off < 0) {
975         /* Attempt to write past end of buffer.  For some reason, this
976            SRW diagnostic is deprecated, but it's so perfect for our
977            purposes that it would be stupid not to use it. */
978         char numbuf[30];
979         sprintf(numbuf, "%ld", (long) info.max);
980         cql_transform_set_error(ct, YAZ_SRW_TOO_MANY_CHARS_IN_QUERY, numbuf);
981         return -1;
982     }
983     if (info.off >= 0)
984         info.buf[info.off] = '\0';
985     return r;
986 }
987
988 int cql_transform_error(cql_transform_t ct, const char **addinfo)
989 {
990     *addinfo = wrbuf_len(ct->addinfo) ? wrbuf_cstr(ct->addinfo) : 0;
991     return ct->error;
992 }
993
994 void cql_transform_set_error(cql_transform_t ct, int error, const char *addinfo)
995 {
996     wrbuf_rewind(ct->addinfo);
997     if (addinfo)
998         wrbuf_puts(ct->addinfo, addinfo);
999     ct->error = error;
1000 }
1001
1002 /*
1003  * Local variables:
1004  * c-basic-offset: 4
1005  * c-file-style: "Stroustrup"
1006  * indent-tabs-mode: nil
1007  * End:
1008  * vim: shiftwidth=4 tabstop=8 expandtab
1009  */
1010