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