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