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