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