Make cql_transform_rpn2cql_{stream,wrbuf} thread-safe
[yaz-moved-to-github.git] / src / solrtransform.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 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, split out into 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 /* Utility 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 static int compare_attr(Z_AttributeElement *a, Z_AttributeElement *b)
311 {
312     ODR odr_a = odr_createmem(ODR_ENCODE);
313     ODR odr_b = odr_createmem(ODR_ENCODE);
314     int len_a, len_b;
315     char *buf_a, *buf_b;
316     int ret;
317
318     z_AttributeElement(odr_a, &a, 0, 0);
319     z_AttributeElement(odr_b, &b, 0, 0);
320
321     buf_a = odr_getbuf(odr_a, &len_a, 0);
322     buf_b = odr_getbuf(odr_b, &len_b, 0);
323
324     ret = yaz_memcmp(buf_a, buf_b, len_a, len_b);
325
326     odr_destroy(odr_a);
327     odr_destroy(odr_b);
328     return ret;
329 }
330
331 const char *solr_lookup_reverse(solr_transform_t ct,
332                                const char *category,
333                                Z_AttributeList *attributes)
334 {
335     struct solr_prop_entry *e;
336     size_t clen = strlen(category);
337     for (e = ct->entry; e; e = e->next)
338     {
339         if (!strncmp(e->pattern, category, clen))
340         {
341             /* category matches.. See if attributes in pattern value
342                are all listed in actual attributes */
343             int i;
344             for (i = 0; i < e->attr_list.num_attributes; i++)
345             {
346                 /* entry attribute */
347                 Z_AttributeElement *e_ae = e->attr_list.attributes[i];
348                 int j;
349                 for (j = 0; j < attributes->num_attributes; j++)
350                 {
351                     /* actual attribute */
352                     Z_AttributeElement *a_ae = attributes->attributes[j];
353                     int r = compare_attr(e_ae, a_ae);
354                     if (r == 0)
355                         break;
356                 }
357                 if (j == attributes->num_attributes)
358                     break; /* i was not found at all.. try next pattern */
359
360             }
361             if (i == e->attr_list.num_attributes)
362                 return e->pattern + clen;
363         }
364     }
365     return 0;
366 }
367
368 static const char *solr_lookup_property(solr_transform_t ct,
369                                        const char *pat1, const char *pat2,
370                                        const char *pat3)
371 {
372     char pattern[120];
373     struct solr_prop_entry *e;
374
375     if (pat1 && pat2 && pat3)
376         sprintf(pattern, "%.39s.%.39s.%.39s", pat1, pat2, pat3);
377     else if (pat1 && pat2)
378         sprintf(pattern, "%.39s.%.39s", pat1, pat2);
379     else if (pat1 && pat3)
380         sprintf(pattern, "%.39s.%.39s", pat1, pat3);
381     else if (pat1)
382         sprintf(pattern, "%.39s", pat1);
383     else
384         return 0;
385
386     for (e = ct->entry; e; e = e->next)
387     {
388         if (!solr_strcmp(e->pattern, pattern))
389             return e->value;
390     }
391     return 0;
392 }
393
394 int solr_pr_attr_uri(solr_transform_t ct, const char *category,
395                    const char *uri, const char *val, const char *default_val,
396                    void (*pr)(const char *buf, void *client_data),
397                    void *client_data,
398                    int errcode)
399 {
400     const char *res = 0;
401     const char *eval = val ? val : default_val;
402     const char *prefix = 0;
403
404     if (uri)
405     {
406         struct solr_prop_entry *e;
407
408         for (e = ct->entry; e; e = e->next)
409             if (!memcmp(e->pattern, "set.", 4) && e->value &&
410                 !strcmp(e->value, uri))
411             {
412                 prefix = e->pattern+4;
413                 break;
414             }
415         /* must have a prefix now - if not it's an error */
416     }
417
418     if (!uri || prefix)
419     {
420         if (!res)
421             res = solr_lookup_property(ct, category, prefix, eval);
422         /* we have some aliases for some relations unfortunately.. */
423         if (!res && !prefix && !strcmp(category, "relation"))
424         {
425             if (!strcmp(val, "=="))
426                 res = solr_lookup_property(ct, category, prefix, "exact");
427             if (!strcmp(val, "="))
428                 res = solr_lookup_property(ct, category, prefix, "eq");
429             if (!strcmp(val, "<="))
430                 res = solr_lookup_property(ct, category, prefix, "le");
431             if (!strcmp(val, ">="))
432                 res = solr_lookup_property(ct, category, prefix, "ge");
433         }
434         if (!res)
435             res = solr_lookup_property(ct, category, prefix, "*");
436     }
437     if (res)
438     {
439         char buf[64];
440
441         const char *cp0 = res, *cp1;
442         while ((cp1 = strchr(cp0, '=')))
443         {
444             int i;
445             while (*cp1 && *cp1 != ' ')
446                 cp1++;
447             if (cp1 - cp0 >= (ptrdiff_t) sizeof(buf))
448                 break;
449             memcpy(buf, cp0, cp1 - cp0);
450             buf[cp1-cp0] = 0;
451             (*pr)("@attr ", client_data);
452
453             for (i = 0; buf[i]; i++)
454             {
455                 if (buf[i] == '*')
456                     (*pr)(eval, client_data);
457                 else
458                 {
459                     char tmp[2];
460                     tmp[0] = buf[i];
461                     tmp[1] = '\0';
462                     (*pr)(tmp, client_data);
463                 }
464             }
465             (*pr)(" ", client_data);
466             cp0 = cp1;
467             while (*cp0 == ' ')
468                 cp0++;
469         }
470         return 1;
471     }
472     /* error ... */
473     if (errcode && !ct->error)
474     {
475         ct->error = errcode;
476         if (val)
477             ct->addinfo = xstrdup(val);
478         else
479             ct->addinfo = 0;
480     }
481     return 0;
482 }
483
484 int solr_pr_attr(solr_transform_t ct, const char *category,
485                 const char *val, const char *default_val,
486                 void (*pr)(const char *buf, void *client_data),
487                 void *client_data,
488                 int errcode)
489 {
490     return solr_pr_attr_uri(ct, category, 0 /* uri */,
491                            val, default_val, pr, client_data, errcode);
492 }
493
494
495 static void solr_pr_int(int val,
496                        void (*pr)(const char *buf, void *client_data),
497                        void *client_data)
498 {
499     char buf[21];              /* enough characters to 2^64 */
500     sprintf(buf, "%d", val);
501     (*pr)(buf, client_data);
502     (*pr)(" ", client_data);
503 }
504
505
506 static int solr_pr_prox(solr_transform_t ct, struct solr_node *mods,
507                        void (*pr)(const char *buf, void *client_data),
508                        void *client_data)
509 {
510     int exclusion = 0;
511     int distance = -1;
512     int ordered = 0;
513     int proxrel = 2;            /* less than or equal */
514     int unit = 2;               /* word */
515
516     while (mods)
517     {
518         const char *name = mods->u.st.index;
519         const char *term = mods->u.st.term;
520         const char *relation = mods->u.st.relation;
521
522         if (!strcmp(name, "distance")) {
523             distance = strtol(term, (char**) 0, 0);
524             if (!strcmp(relation, "="))
525                 proxrel = 3;
526             else if (!strcmp(relation, ">"))
527                 proxrel = 5;
528             else if (!strcmp(relation, "<"))
529                 proxrel = 1;
530             else if (!strcmp(relation, ">="))
531                 proxrel = 4;
532             else if (!strcmp(relation, "<="))
533                 proxrel = 2;
534             else if (!strcmp(relation, "<>"))
535                 proxrel = 6;
536             else
537             {
538                 ct->error = YAZ_SRW_UNSUPP_PROX_RELATION;
539                 ct->addinfo = xstrdup(relation);
540                 return 0;
541             }
542         }
543         else if (!strcmp(name, "ordered"))
544             ordered = 1;
545         else if (!strcmp(name, "unordered"))
546             ordered = 0;
547         else if (!strcmp(name, "unit"))
548         {
549             if (!strcmp(term, "word"))
550                 unit = 2;
551             else if (!strcmp(term, "sentence"))
552                 unit = 3;
553             else if (!strcmp(term, "paragraph"))
554                 unit = 4;
555             else if (!strcmp(term, "element"))
556                 unit = 8;
557             else
558             {
559                 ct->error = YAZ_SRW_UNSUPP_PROX_UNIT;
560                 ct->addinfo = xstrdup(term);
561                 return 0;
562             }
563         }
564         else
565         {
566             ct->error = YAZ_SRW_UNSUPP_BOOLEAN_MODIFIER;
567             ct->addinfo = xstrdup(name);
568             return 0;
569         }
570         mods = mods->u.st.modifiers;
571     }
572
573     if (distance == -1)
574         distance = (unit == 2) ? 1 : 0;
575
576     solr_pr_int(exclusion, pr, client_data);
577     solr_pr_int(distance, pr, client_data);
578     solr_pr_int(ordered, pr, client_data);
579     solr_pr_int(proxrel, pr, client_data);
580     (*pr)("k ", client_data);
581     solr_pr_int(unit, pr, client_data);
582
583     return 1;
584 }
585
586 /* Returns location of first wildcard character in the `length'
587  * characters starting at `term', or a null pointer of there are
588  * none -- like memchr().
589  */
590 static const char *wcchar(int start, const char *term, int length)
591 {
592     while (length > 0)
593     {
594         if (start || term[-1] != '\\')
595             if (strchr("*?", *term))
596                 return term;
597         term++;
598         length--;
599         start = 0;
600     }
601     return 0;
602 }
603
604
605 /* ### checks for SOLR relation-name rather than Type-1 attribute */
606 static int has_modifier(struct solr_node *cn, const char *name) {
607     struct solr_node *mod;
608     for (mod = cn->u.st.modifiers; mod != 0; mod = mod->u.st.modifiers) {
609         if (!strcmp(mod->u.st.index, name))
610             return 1;
611     }
612
613     return 0;
614 }
615
616
617 static void emit_term(solr_transform_t ct,
618                       struct solr_node *cn,
619                       const char *term, int length,
620                       void (*pr)(const char *buf, void *client_data),
621                       void *client_data)
622 {
623     int i;
624     const char *ns = cn->u.st.index_uri;
625     int process_term = !has_modifier(cn, "regexp");
626     char *z3958_mem = 0;
627
628     assert(cn->which == SOLR_NODE_ST);
629
630     if (process_term && length > 0)
631     {
632         if (length > 1 && term[0] == '^' && term[length-1] == '^')
633         {
634             solr_pr_attr(ct, "position", "firstAndLast", 0,
635                         pr, client_data, YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
636             term++;
637             length -= 2;
638         }
639         else if (term[0] == '^')
640         {
641             solr_pr_attr(ct, "position", "first", 0,
642                         pr, client_data, YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
643             term++;
644             length--;
645         }
646         else if (term[length-1] == '^')
647         {
648             solr_pr_attr(ct, "position", "last", 0,
649                         pr, client_data, YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
650             length--;
651         }
652         else
653         {
654             solr_pr_attr(ct, "position", "any", 0,
655                         pr, client_data, YAZ_SRW_ANCHORING_CHAR_IN_UNSUPP_POSITION);
656         }
657     }
658
659     if (process_term && length > 0)
660     {
661         const char *first_wc = wcchar(1, term, length);
662         const char *second_wc = first_wc ?
663             wcchar(0, first_wc+1, length-(first_wc-term)-1) : 0;
664
665         /* Check for well-known globbing patterns that represent
666          * simple truncation attributes as expected by, for example,
667          * Bath-compliant server.  If we find such a pattern but
668          * there's no mapping for it, that's fine: we just use a
669          * general pattern-matching attribute.
670          */
671         if (first_wc == term && second_wc == term + length-1
672             && *first_wc == '*' && *second_wc == '*'
673             && solr_pr_attr(ct, "truncation", "both", 0, pr, client_data, 0))
674         {
675             term++;
676             length -= 2;
677         }
678         else if (first_wc == term && second_wc == 0 && *first_wc == '*'
679                  && solr_pr_attr(ct, "truncation", "left", 0,
680                                 pr, client_data, 0))
681         {
682             term++;
683             length--;
684         }
685         else if (first_wc == term + length-1 && second_wc == 0
686                  && *first_wc == '*'
687                  && solr_pr_attr(ct, "truncation", "right", 0,
688                                 pr, client_data, 0))
689         {
690             length--;
691         }
692         else if (first_wc)
693         {
694             /* We have one or more wildcard characters, but not in a
695              * way that can be dealt with using only the standard
696              * left-, right- and both-truncation attributes.  We need
697              * to translate the pattern into a Z39.58-type pattern,
698              * which has been supported in BIB-1 since 1996.  If
699              * there's no configuration element for "truncation.z3958"
700              * we indicate this as error 28 "Masking character not
701              * supported".
702              */
703             int i;
704             solr_pr_attr(ct, "truncation", "z3958", 0,
705                         pr, client_data, YAZ_SRW_MASKING_CHAR_UNSUPP);
706             z3958_mem = (char *) xmalloc(length+1);
707             for (i = 0; i < length; i++)
708             {
709                 if (i > 0 && term[i-1] == '\\')
710                     z3958_mem[i] = term[i];
711                 else if (term[i] == '*')
712                     z3958_mem[i] = '?';
713                 else if (term[i] == '?')
714                     z3958_mem[i] = '#';
715                 else
716                     z3958_mem[i] = term[i];
717             }
718             z3958_mem[length] = '\0';
719             term = z3958_mem;
720         }
721         else {
722             /* No masking characters.  Use "truncation.none" if given. */
723             solr_pr_attr(ct, "truncation", "none", 0,
724                         pr, client_data, 0);
725         }
726     }
727     if (ns) {
728         solr_pr_attr_uri(ct, "index", ns,
729                         cn->u.st.index, "serverChoice",
730                         pr, client_data, YAZ_SRW_UNSUPP_INDEX);
731     }
732     if (cn->u.st.modifiers)
733     {
734         struct solr_node *mod = cn->u.st.modifiers;
735         for (; mod; mod = mod->u.st.modifiers)
736         {
737             solr_pr_attr(ct, "relationModifier", mod->u.st.index, 0,
738                         pr, client_data, YAZ_SRW_UNSUPP_RELATION_MODIFIER);
739         }
740     }
741
742     (*pr)("\"", client_data);
743     for (i = 0; i<length; i++)
744     {
745         /* pr(int) each character */
746         /* we do not need to deal with \-sequences because the
747            SOLR and PQF terms have same \-format, bug #1988 */
748         char buf[2];
749
750         buf[0] = term[i];
751         buf[1] = '\0';
752         (*pr)(buf, client_data);
753     }
754     (*pr)("\" ", client_data);
755     xfree(z3958_mem);
756 }
757
758 static void emit_terms(solr_transform_t ct,
759                        struct solr_node *cn,
760                        void (*pr)(const char *buf, void *client_data),
761                        void *client_data,
762                        const char *op)
763 {
764     struct solr_node *ne = cn->u.st.extra_terms;
765     if (ne)
766     {
767         (*pr)("@", client_data);
768         (*pr)(op, client_data);
769         (*pr)(" ", client_data);
770     }
771     emit_term(ct, cn, cn->u.st.term, strlen(cn->u.st.term),
772               pr, client_data);
773     for (; ne; ne = ne->u.st.extra_terms)
774     {
775         if (ne->u.st.extra_terms)
776         {
777             (*pr)("@", client_data);
778             (*pr)(op, client_data);
779             (*pr)(" ", client_data);
780         }
781         emit_term(ct, cn, ne->u.st.term, strlen(ne->u.st.term),
782                   pr, client_data);
783     }
784 }
785
786 static void emit_wordlist(solr_transform_t ct,
787                           struct solr_node *cn,
788                           void (*pr)(const char *buf, void *client_data),
789                           void *client_data,
790                           const char *op)
791 {
792     const char *cp0 = cn->u.st.term;
793     const char *cp1;
794     const char *last_term = 0;
795     int last_length = 0;
796     while(cp0)
797     {
798         while (*cp0 == ' ')
799             cp0++;
800         cp1 = strchr(cp0, ' ');
801         if (last_term)
802         {
803             (*pr)("@", client_data);
804             (*pr)(op, client_data);
805             (*pr)(" ", client_data);
806             emit_term(ct, cn, last_term, last_length, pr, client_data);
807         }
808         last_term = cp0;
809         if (cp1)
810             last_length = cp1 - cp0;
811         else
812             last_length = strlen(cp0);
813         cp0 = cp1;
814     }
815     if (last_term)
816         emit_term(ct, cn, last_term, last_length, pr, client_data);
817 }
818
819 void solr_transform_r(solr_transform_t ct,
820                      struct solr_node *cn,
821                      void (*pr)(const char *buf, void *client_data),
822                      void *client_data)
823 {
824     const char *ns;
825     struct solr_node *mods;
826
827     if (!cn)
828         return;
829     switch (cn->which)
830     {
831     case SOLR_NODE_ST:
832         ns = cn->u.st.index_uri;
833         if (ns)
834         {
835             /* TODO If relevant fix with solr_uri */
836             if (!strcmp(ns, solr_uri())
837                 && cn->u.st.index && !solr_strcmp(cn->u.st.index, "resultSet"))
838             {
839                 (*pr)("@set \"", client_data);
840                 (*pr)(cn->u.st.term, client_data);
841                 (*pr)("\" ", client_data);
842                 return ;
843             }
844         }
845         else
846         {
847             if (!ct->error)
848             {
849                 ct->error = YAZ_SRW_UNSUPP_CONTEXT_SET;
850                 ct->addinfo = 0;
851             }
852         }
853         solr_pr_attr(ct, "always", 0, 0, pr, client_data, 0);
854         solr_pr_attr(ct, "relation", cn->u.st.relation, 0, pr, client_data,
855                     YAZ_SRW_UNSUPP_RELATION);
856         solr_pr_attr(ct, "structure", cn->u.st.relation, 0,
857                     pr, client_data, YAZ_SRW_UNSUPP_COMBI_OF_RELATION_AND_TERM);
858         if (cn->u.st.relation && !solr_strcmp(cn->u.st.relation, "all"))
859             emit_wordlist(ct, cn, pr, client_data, "and");
860         else if (cn->u.st.relation && !solr_strcmp(cn->u.st.relation, "any"))
861             emit_wordlist(ct, cn, pr, client_data, "or");
862         else
863             emit_terms(ct, cn, pr, client_data, "and");
864         break;
865     case SOLR_NODE_BOOL:
866         (*pr)("@", client_data);
867         (*pr)(cn->u.boolean.value, client_data);
868         (*pr)(" ", client_data);
869         mods = cn->u.boolean.modifiers;
870         if (!strcmp(cn->u.boolean.value, "prox"))
871         {
872             if (!solr_pr_prox(ct, mods, pr, client_data))
873                 return;
874         }
875         else if (mods)
876         {
877             /* Boolean modifiers other than on proximity not supported */
878             ct->error = YAZ_SRW_UNSUPP_BOOLEAN_MODIFIER;
879             ct->addinfo = xstrdup(mods->u.st.index);
880             return;
881         }
882
883         solr_transform_r(ct, cn->u.boolean.left, pr, client_data);
884         solr_transform_r(ct, cn->u.boolean.right, pr, client_data);
885         break;
886
887     default:
888         fprintf(stderr, "Fatal: impossible SOLR node-type %d\n", cn->which);
889         abort();
890     }
891 }
892
893 int solr_transform(solr_transform_t ct, struct solr_node *cn,
894                   void (*pr)(const char *buf, void *client_data),
895                   void *client_data)
896 {
897     struct solr_prop_entry *e;
898     NMEM nmem = nmem_create();
899
900     ct->error = 0;
901     xfree(ct->addinfo);
902     ct->addinfo = 0;
903
904     for (e = ct->entry; e ; e = e->next)
905     {
906         /* TODO remove as SOLR dont supports sets.
907         if (!solr_strncmp(e->pattern, "set.", 4))
908             solr_apply_prefix(nmem, cn, e->pattern+4, e->value);
909         else if (!solr_strcmp(e->pattern, "set"))
910             solr_apply_prefix(nmem, cn, 0, e->value);
911          */
912     }
913     solr_transform_r(ct, cn, pr, client_data);
914     nmem_destroy(nmem);
915     return ct->error;
916 }
917
918
919 int solr_transform_FILE(solr_transform_t ct, struct solr_node *cn, FILE *f)
920 {
921     /* We can use the cql_fputs util */
922     return solr_transform(ct, cn, cql_fputs, f);
923 }
924
925 int solr_transform_buf(solr_transform_t ct, struct solr_node *cn, char *out, int max)
926 {
927     struct solr_buf_write_info info;
928     int r;
929
930     info.off = 0;
931     info.max = max;
932     info.buf = out;
933     r = solr_transform(ct, cn, cql_buf_write_handler, &info);
934     if (info.off < 0) {
935         /* Attempt to write past end of buffer.  For some reason, this
936            SRW diagnostic is deprecated, but it's so perfect for our
937            purposes that it would be stupid not to use it. */
938         char numbuf[30];
939         ct->error = YAZ_SRW_TOO_MANY_CHARS_IN_QUERY;
940         sprintf(numbuf, "%ld", (long) info.max);
941         ct->addinfo = xstrdup(numbuf);
942         return -1;
943     }
944     if (info.off >= 0)
945         info.buf[info.off] = '\0';
946     return r;
947 }
948
949 int solr_transform_error(solr_transform_t ct, const char **addinfo)
950 {
951     *addinfo = ct->addinfo;
952     return ct->error;
953 }
954
955 void solr_transform_set_error(solr_transform_t ct, int error, const char *addinfo)
956 {
957     xfree(ct->addinfo);
958     ct->addinfo = addinfo ? xstrdup(addinfo) : 0;
959     ct->error = error;
960 }
961
962 /*
963  * Local variables:
964  * c-basic-offset: 4
965  * c-file-style: "Stroustrup"
966  * indent-tabs-mode: nil
967  * End:
968  * vim: shiftwidth=4 tabstop=8 expandtab
969  */
970