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