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