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