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