zebrasrv: sortkeys args are optional
[idzebra-moved-to-github.git] / index / recgrs.c
1 /* This file is part of the Zebra server.
2    Copyright (C) 1994-2011 Index Data
3
4 Zebra is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 */
19
20 #if HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23 #include <stdio.h>
24 #include <assert.h>
25 #include <sys/types.h>
26 #include <ctype.h>
27
28 #include <yaz/log.h>
29 #include <yaz/oid_db.h>
30 #include <yaz/diagbib1.h>
31
32 #include <d1_absyn.h>
33 #include <idzebra/recgrs.h>
34
35 #define GRS_MAX_WORD 512
36
37 struct source_parser {
38     int len;
39     const char *tok;
40     const char *src;
41     int lookahead;
42     NMEM nmem;
43 };
44
45 static int sp_lex(struct source_parser *sp)
46 {
47     while (*sp->src == ' ')
48         (sp->src)++;
49     sp->tok = sp->src;
50     sp->len = 0;
51     while (*sp->src && !strchr("<>();,-: ", *sp->src))
52     {
53         sp->src++;
54         sp->len++;
55     }
56     if (sp->len)
57         sp->lookahead = 't';
58     else
59     {
60         sp->lookahead = *sp->src;
61         if (*sp->src)
62             sp->src++;
63     }
64     return sp->lookahead;
65 }
66
67 static int sp_expr(struct source_parser *sp, data1_node *n, RecWord *wrd);
68
69 static int sp_range(struct source_parser *sp, data1_node *n, RecWord *wrd)
70 {
71     int start, len;
72     RecWord tmp_w;
73     
74     /* ( */
75     sp_lex(sp);
76     if (sp->lookahead != '(')
77         return 0;
78     sp_lex(sp); /* skip ( */
79     
80     /* 1st arg: string */
81     if (!sp_expr(sp, n, wrd))
82         return 0;
83     
84     if (sp->lookahead != ',')
85         return 0;       
86     sp_lex(sp); /* skip , */
87     
88     /* 2nd arg: start */
89     if (!sp_expr(sp, n, &tmp_w))
90         return 0;
91     start = atoi_n(tmp_w.term_buf, tmp_w.term_len);
92     
93     if (sp->lookahead == ',')
94     {
95         sp_lex(sp); /* skip , */
96         
97         /* 3rd arg: length */
98         if (!sp_expr(sp, n, &tmp_w))
99             return 0;
100         len = atoi_n(tmp_w.term_buf, tmp_w.term_len);
101     }
102     else
103         len = wrd->term_len;
104     
105     /* ) */
106     if (sp->lookahead != ')')
107         return 0;       
108     sp_lex(sp);
109     
110     if (wrd->term_buf)
111     {
112         if (start >= wrd->term_len)
113             wrd->term_len = 0;
114         else
115         {
116             wrd->term_len -= start;
117             wrd->term_buf += start;
118
119             if (wrd->term_len > len)
120                 wrd->term_len = len;
121         }
122     }
123     return 1;
124 }
125
126 static int sp_first(struct source_parser *sp, data1_node *n, RecWord *wrd)
127 {
128     char num_str[20];
129     int min_pos = -1;
130     sp_lex(sp);
131     if (sp->lookahead != '(')
132         return 0;
133     sp_lex(sp); /* skip ( */
134     if (!sp_expr(sp, n, wrd))
135         return 0;
136     while (sp->lookahead == ',')
137     {
138         RecWord search_w;
139         int i;
140         sp_lex(sp); /* skip , */
141         
142         if (!sp_expr(sp, n, &search_w))
143             return 0;
144         for (i = 0; i<wrd->term_len; i++)
145         {
146             int j;
147             for (j = 0; j<search_w.term_len && i+j < wrd->term_len; j++)
148                 if (wrd->term_buf[i+j] != search_w.term_buf[j])
149                     break;
150             if (j == search_w.term_len) /* match ? */
151             {
152                 if (min_pos == -1 || i < min_pos)
153                     min_pos = i;
154                 break;
155             }
156         }
157     }
158     if (sp->lookahead != ')')
159         return 0;
160     sp_lex(sp);
161     if (min_pos == -1)
162         min_pos = 0;  /* the default if not found */
163     sprintf(num_str, "%d", min_pos);
164     wrd->term_buf = nmem_strdup(sp->nmem, num_str);
165     wrd->term_len = strlen(wrd->term_buf);
166     return 1;
167 }
168
169 static int sp_expr(struct source_parser *sp, data1_node *n, RecWord *wrd)
170 {
171     if (sp->lookahead != 't')
172         return 0;
173     if (sp->len == 4 && !memcmp(sp->tok, "data", sp->len))
174     {
175         if (n->which == DATA1N_data)
176         {
177             wrd->term_buf = n->u.data.data;
178             wrd->term_len = n->u.data.len;
179         }
180         sp_lex(sp);
181     }
182     else if (sp->len == 3 && !memcmp(sp->tok, "tag", sp->len))
183     {
184         if (n->which == DATA1N_tag)
185         {               
186             wrd->term_buf = n->u.tag.tag;
187             wrd->term_len = strlen(n->u.tag.tag);
188         }
189         sp_lex(sp);
190     }
191     else if (sp->len == 4 && !memcmp(sp->tok, "attr", sp->len))
192     {
193         RecWord tmp_w;
194         sp_lex(sp);
195         if (sp->lookahead != '(')
196             return 0;
197         sp_lex(sp);
198
199         if (!sp_expr(sp, n, &tmp_w))
200             return 0;
201         
202         wrd->term_buf = "";
203         wrd->term_len = 0;
204         if (n->which == DATA1N_tag)
205         {
206             data1_xattr *p = n->u.tag.attributes;
207             while (p && strlen(p->name) != tmp_w.term_len && 
208                    memcmp (p->name, tmp_w.term_buf, tmp_w.term_len))
209                 p = p->next;
210             if (p)
211             {
212                 wrd->term_buf = p->value;
213                 wrd->term_len = strlen(p->value);
214             }
215         }
216         if (sp->lookahead != ')')
217             return 0;
218         sp_lex(sp);
219     }
220     else if (sp->len == 5 && !memcmp(sp->tok, "first", sp->len))
221     {
222         return sp_first(sp, n, wrd);
223     }
224     else if (sp->len == 5 && !memcmp(sp->tok, "range", sp->len))
225     {
226         return sp_range(sp, n, wrd);
227     }
228     else if (sp->len > 0 && isdigit(*(unsigned char *)sp->tok))
229     {
230         char *b;
231         wrd->term_len = sp->len;
232         b = nmem_malloc(sp->nmem, sp->len);
233         memcpy(b, sp->tok, sp->len);
234         wrd->term_buf = b;
235         sp_lex(sp);
236     }
237     else if (sp->len > 2 && sp->tok[0] == '\'' && sp->tok[sp->len-1] == '\'')
238     {
239         char *b;
240         wrd->term_len = sp->len - 2;
241         b = nmem_malloc(sp->nmem, wrd->term_len);
242         memcpy(b, sp->tok+1, wrd->term_len);
243         wrd->term_buf = b;
244         sp_lex(sp);
245     }
246     else 
247     {
248         wrd->term_buf = "";
249         wrd->term_len = 0;
250         sp_lex(sp);
251     }
252     return 1;
253 }
254
255 static struct source_parser *source_parser_create(void)
256 {
257     struct source_parser *sp = xmalloc(sizeof(*sp));
258
259     sp->nmem = nmem_create();
260     return sp;
261 }
262
263 static void source_parser_destroy(struct source_parser *sp)
264 {
265     if (!sp)
266         return;
267     nmem_destroy(sp->nmem);
268     xfree(sp);
269 }
270     
271 static int sp_parse(struct source_parser *sp, 
272                     data1_node *n, RecWord *wrd, const char *src)
273 {
274     sp->len = 0;
275     sp->tok = 0;
276     sp->src = src;
277     sp->lookahead = 0;
278     nmem_reset(sp->nmem);
279
280     sp_lex(sp);
281     return sp_expr(sp, n, wrd);
282 }
283
284 int d1_check_xpath_predicate(data1_node *n, struct xpath_predicate *p)
285 {
286     int res = 1;
287     char *attname;
288     data1_xattr *attr;
289     
290     if (!p) {
291         return 1;
292     } else {
293         if (p->which == XPATH_PREDICATE_RELATION) {
294             if (p->u.relation.name[0]) {
295                 if (*p->u.relation.name != '@') {
296                     yaz_log(YLOG_WARN, 
297                          "  Only attributes (@) are supported in xelm xpath predicates");
298                     yaz_log(YLOG_WARN, "predicate %s ignored", p->u.relation.name);
299                     return 1;
300                 }
301                 attname = p->u.relation.name + 1;
302                 res = 0;
303                 /* looking for the attribute with a specified name */
304                 for (attr = n->u.tag.attributes; attr; attr = attr->next) {
305                     if (!strcmp(attr->name, attname)) {
306                         if (p->u.relation.op[0]) {
307                             if (*p->u.relation.op != '=') {
308                                 yaz_log(YLOG_WARN, 
309                                      "Only '=' relation is supported (%s)",p->u.relation.op);
310                                 yaz_log(YLOG_WARN, "predicate %s ignored", p->u.relation.name);
311                                 res = 1; break;
312                             } else {
313                                 if (!strcmp(attr->value, p->u.relation.value)) {
314                                     res = 1; break;
315                                 } 
316                             }
317                         } else {
318                             /* attribute exists, no value specified */
319                             res = 1; break;
320                         }
321                     }
322                 }
323                 return res;
324             } else {
325                 return 1;
326             }
327         } 
328         else if (p->which == XPATH_PREDICATE_BOOLEAN) {
329             if (!strcmp(p->u.boolean.op,"and")) {
330                 return d1_check_xpath_predicate(n, p->u.boolean.left) 
331                     && d1_check_xpath_predicate(n, p->u.boolean.right); 
332             }
333             else if (!strcmp(p->u.boolean.op,"or")) {
334                 return (d1_check_xpath_predicate(n, p->u.boolean.left) 
335                         || d1_check_xpath_predicate(n, p->u.boolean.right)); 
336             } else {
337                 yaz_log(YLOG_WARN, "Unknown boolean relation %s, ignored",p->u.boolean.op);
338                 return 1;
339             }
340         }
341     }
342     return 0;
343 }
344
345
346 static int dfa_match_first(struct DFA_state **dfaar, const char *text)
347 {
348     struct DFA_state *s = dfaar[0]; /* start state */
349     struct DFA_tran *t;
350     int i;
351     const char *p = text;
352     unsigned char c;
353     
354     for (c = *p++, t = s->trans, i = s->tran_no; --i >= 0; t++)
355     {
356         if (c >= t->ch[0] && c <= t->ch[1])
357         {
358             while (i >= 0)
359             {
360                 /* move to next state and return if we get a match */
361                 s = dfaar[t->to];
362                 if (s->rule_no)
363                     return 1;
364                 /* next char */
365                 if (!c)
366                     return 0;
367                 c = *p++;
368                 for (t = s->trans, i = s->tran_no; --i >= 0; t++)
369                     if (c >= t->ch[0] && c <= t->ch[1])
370                         break;
371             }
372         }
373     }
374     return 0;
375 }
376
377 /* *ostrich*
378    
379 New function, looking for xpath "element" definitions in abs, by
380 tagpath, using a kind of ugly regxp search.The DFA was built while
381 parsing abs, so here we just go trough them and try to match
382 against the given tagpath. The first matching entry is returned.
383
384 pop, 2002-12-13
385
386 Added support for enhanced xelm. Now [] predicates are considered
387 as well, when selecting indexing rules... (why the hell it's called
388 termlist???)
389
390 pop, 2003-01-17
391
392 */
393
394 data1_termlist *xpath_termlist_by_tagpath(char *tagpath, data1_node *n)
395 {
396     data1_absyn *abs = n->root->u.root.absyn;
397
398     data1_xpelement *xpe = 0;
399     data1_node *nn;
400 #ifdef ENHANCED_XELM 
401     struct xpath_location_step *xp;
402 #endif
403     char *pexpr = xmalloc(strlen(tagpath)+5);
404     
405     sprintf(pexpr, "/%s\n", tagpath);
406
407     for (xpe = abs->xp_elements; xpe; xpe = xpe->next)
408         xpe->match_state = -1; /* don't know if it matches yet */
409
410     for (xpe = abs->xp_elements; xpe; xpe = xpe->next)
411     {
412         int i;
413         int ok = xpe->match_state;
414         if (ok == -1)
415         {   /* don't know whether there is a match yet */
416             data1_xpelement *xpe1;
417
418             assert(xpe->dfa);
419             ok = dfa_match_first(xpe->dfa->states, pexpr);
420
421 #if OPTIMIZE_MELM
422             /* mark this and following ones with same regexp */
423             for (xpe1 = xpe; xpe1; xpe1 = xpe1->match_next)
424                 xpe1->match_state = ok;
425 #endif
426         }
427         assert(ok == 0 || ok == 1);
428         if (ok) {
429 #ifdef ENHANCED_XELM 
430             /* we have to check the perdicates up to the root node */
431             xp = xpe->xpath;
432             
433             /* find the first tag up in the node structure */
434             for (nn = n; nn && nn->which != DATA1N_tag; nn = nn->parent)
435                 ;
436             
437             /* go from inside out in the node structure, while going
438                backwards trough xpath location steps ... */
439             for (i = xpe->xpath_len - 1; i>0; i--)
440             {
441                 if (!d1_check_xpath_predicate(nn, xp[i].predicate))
442                 {
443                     ok = 0;
444                     break;
445                 }
446                 
447                 if (nn->which == DATA1N_tag)
448                     nn = nn->parent;
449             }
450 #endif
451             if (ok)
452                 break;
453         }
454     } 
455     
456     xfree(pexpr);
457     
458     if (xpe) {
459         return xpe->termlists;
460     } else {
461         return NULL;
462     }
463 }
464
465 /* use
466      1   start element (tag)
467      2   end element
468      3   start attr (and attr-exact)
469      4   end attr
470
471   1016   cdata
472   1015   attr data
473
474   *ostrich*
475
476   Now, if there is a matching xelm described in abs, for the
477   indexed element or the attribute,  then the data is handled according 
478   to those definitions...
479
480   modified by pop, 2002-12-13
481 */
482
483 /* add xpath index for an attribute */
484 static void index_xpath_attr(char *tag_path, char *name, char *value,
485                               char *structure, struct recExtractCtrl *p,
486                               RecWord *wrd)
487 {
488     wrd->index_name = ZEBRA_XPATH_ELM_BEGIN;
489     wrd->index_type = "0";
490     wrd->term_buf = tag_path;
491     wrd->term_len = strlen(tag_path);
492     (*p->tokenAdd)(wrd);
493     
494     if (value) {
495         wrd->index_name = ZEBRA_XPATH_ATTR_CDATA;
496         wrd->index_type = "w";
497         wrd->term_buf = value;
498         wrd->term_len = strlen(value);
499         (*p->tokenAdd)(wrd);
500     }
501     wrd->index_name = ZEBRA_XPATH_ELM_END;
502     wrd->index_type = "0";
503     wrd->term_buf = tag_path;
504     wrd->term_len = strlen(tag_path);
505     (*p->tokenAdd)(wrd);
506 }
507
508
509 static void mk_tag_path_full(char *tag_path_full, size_t max, data1_node *n)
510 {
511     size_t flen = 0;
512     data1_node *nn;
513
514     /* we have to fetch the whole path to the data tag */
515     for (nn = n; nn; nn = nn->parent)
516     {
517         if (nn->which == DATA1N_tag)
518         {
519             size_t tlen = strlen(nn->u.tag.tag);
520             if (tlen + flen > (max - 2))
521                 break;
522             memcpy(tag_path_full + flen, nn->u.tag.tag, tlen);
523             flen += tlen;
524             tag_path_full[flen++] = '/';
525         }
526         else
527             if (nn->which == DATA1N_root)
528                 break;
529     }
530     tag_path_full[flen] = 0;
531 }
532         
533
534 static void index_xpath(struct source_parser *sp, data1_node *n,
535                         struct recExtractCtrl *p,
536                         int level, RecWord *wrd,
537                         char *xpath_index,
538                         int xpath_is_start
539     )
540 {
541     int i;
542     char tag_path_full[1024];
543     int termlist_only = 1;
544     data1_termlist *tl;
545
546     if (!n->root->u.root.absyn 
547         || 
548         n->root->u.root.absyn->xpath_indexing == DATA1_XPATH_INDEXING_ENABLE)
549     {
550         termlist_only = 0;
551     }
552
553
554     switch (n->which)
555     {
556     case DATA1N_data:
557         wrd->term_buf = n->u.data.data;
558         wrd->term_len = n->u.data.len;
559
560         mk_tag_path_full(tag_path_full, sizeof(tag_path_full), n);
561         
562         /* If we have a matching termlist... */
563         if (n->root->u.root.absyn && 
564             (tl = xpath_termlist_by_tagpath(tag_path_full, n)))
565         {
566             zint max_seqno = 0;
567             for (; tl; tl = tl->next)
568             {
569                 /* need to copy recword because it may be changed */
570                 RecWord wrd_tl;
571                 wrd->index_type = tl->structure;
572                 memcpy(&wrd_tl, wrd, sizeof(*wrd));
573                 if (tl->source)
574                     sp_parse(sp, n, &wrd_tl, tl->source);
575
576                 /* this is just the old fashioned attribute based index */
577                 wrd_tl.index_name = tl->index_name;
578                 if (p->flagShowRecords)
579                 {
580                     int i;
581                     printf("%*sIdx: [%s]", (level + 1) * 4, "",
582                            tl->structure);
583                     printf("%s %s", tl->index_name, tl->source);
584                     printf(" XData:\"");
585                     for (i = 0; i<wrd_tl.term_len && i < 40; i++)
586                         fputc(wrd_tl.term_buf[i], stdout);
587                     fputc('"', stdout);
588                     if (wrd_tl.term_len > 40)
589                         printf(" ...");
590                     fputc('\n', stdout);
591                 }
592                 else
593                 {
594                     (*p->tokenAdd)(&wrd_tl);
595                 }
596                 if (wrd_tl.seqno > max_seqno)
597                     max_seqno = wrd_tl.seqno;
598             }
599             if (max_seqno)
600                 wrd->seqno = max_seqno;
601                 
602         }
603         /* xpath indexing is done, if there was no termlist given, 
604            or no ! in the termlist, and default indexing is enabled... */
605         if (!p->flagShowRecords && !termlist_only)
606         {
607             wrd->index_name = xpath_index;
608             wrd->index_type = "w";
609             (*p->tokenAdd)(wrd);
610         }
611         break;
612     case DATA1N_tag:
613         mk_tag_path_full(tag_path_full, sizeof(tag_path_full), n);
614
615         wrd->index_type = "0";
616         wrd->term_buf = tag_path_full;
617         wrd->term_len = strlen(tag_path_full);
618         wrd->index_name = xpath_index;
619         if (p->flagShowRecords)
620         {
621             printf("%*s tag=", (level + 1) * 4, "");
622             for (i = 0; i<wrd->term_len && i < 40; i++)
623                 fputc(wrd->term_buf[i], stdout);
624             if (i == 40)
625                 printf(" ..");
626             printf("\n");
627         }
628         else
629         {
630             data1_xattr *xp;
631
632             if (!termlist_only)
633                 (*p->tokenAdd)(wrd);   /* index element pag (AKA tag path) */
634             
635             if (xpath_is_start == 1) /* only for the starting tag... */
636             {
637 #define MAX_ATTR_COUNT 50
638                 data1_termlist *tll[MAX_ATTR_COUNT];
639                 
640                 int i = 0;
641                 for (xp = n->u.tag.attributes; xp; xp = xp->next) {
642                     char comb[512];
643                     char attr_tag_path_full[1024]; 
644                     
645                     /* this could be cached as well */
646                     sprintf(attr_tag_path_full, "@%s/%s",
647                              xp->name, tag_path_full);
648
649                     tll[i] = xpath_termlist_by_tagpath(attr_tag_path_full,n);
650                     
651                     if (!termlist_only)
652                     {
653                         /* attribute  (no value) */
654                         wrd->index_type = "0";
655                         wrd->index_name = ZEBRA_XPATH_ATTR_NAME;
656                         wrd->term_buf = xp->name;
657                         wrd->term_len = strlen(xp->name);
658                         
659                         wrd->seqno--;
660                         (*p->tokenAdd)(wrd);
661                         
662                         if (xp->value 
663                             &&
664                             strlen(xp->name) + strlen(xp->value) < sizeof(comb)-2)
665                         {
666                             /* attribute value exact */
667                             strcpy(comb, xp->name);
668                             strcat(comb, "=");
669                             strcat(comb, xp->value);
670                             
671                             wrd->index_name = ZEBRA_XPATH_ATTR_NAME;
672                             wrd->index_type = "0";
673                             wrd->term_buf = comb;
674                             wrd->term_len = strlen(comb);
675                             wrd->seqno--;
676                             
677                             (*p->tokenAdd)(wrd);
678                         }
679                     }     
680                     i++;
681                 }
682                 
683                 i = 0;
684                 for (xp = n->u.tag.attributes; xp; xp = xp->next) {
685                     data1_termlist *tl;
686                     char attr_tag_path_full[1024];
687                     int xpdone = 0;
688                     
689                     sprintf(attr_tag_path_full, "@%s/%s",
690                              xp->name, tag_path_full);
691                     if ((tl = tll[i]))
692                     {
693                         /* If there is a termlist given (=xelm directive) */
694                         for (; tl; tl = tl->next)
695                         {
696                             if (!tl->index_name)
697                             {
698                                 /* add xpath index for the attribute */
699                                 index_xpath_attr(attr_tag_path_full, xp->name,
700                                                   xp->value, tl->structure,
701                                                   p, wrd);
702                                 xpdone = 1;
703                             } else {
704                                 /* index attribute value (only path/@attr) */
705                                 if (xp->value) 
706                                 {
707                                     wrd->index_name = tl->index_name;
708                                     wrd->index_type = tl->structure;
709                                     wrd->term_buf = xp->value;
710                                     wrd->term_len = strlen(xp->value);
711                                     (*p->tokenAdd)(wrd);
712                                 }
713                             }
714                         }
715                     }
716                     /* if there was no termlist for the given path, 
717                        or the termlist didn't have a ! element, index 
718                        the attribute as "w" */
719                     if (!xpdone && !termlist_only)
720                     {
721                         index_xpath_attr(attr_tag_path_full, xp->name,
722                                           xp->value,  "w", p, wrd);
723                     }
724                     i++;
725                 }
726             }
727         }
728     }
729 }
730
731 static void index_termlist(struct source_parser *sp, data1_node *par,
732                             data1_node *n,
733                             struct recExtractCtrl *p, int level, RecWord *wrd)
734 {
735     data1_termlist *tlist = 0;
736     data1_datatype dtype = DATA1K_string;
737
738     /*
739      * cycle up towards the root until we find a tag with an att..
740      * this has the effect of indexing locally defined tags with
741      * the attribute of their ancestor in the record.
742      */
743     
744     while (!par->u.tag.element)
745         if (!par->parent || !(par=get_parent_tag(p->dh, par->parent)))
746             break;
747     if (!par || !(tlist = par->u.tag.element->termlists))
748         return;
749     if (par->u.tag.element->tag)
750         dtype = par->u.tag.element->tag->kind;
751
752     for (; tlist; tlist = tlist->next)
753     {
754         /* consider source */
755         wrd->term_buf = 0;
756         assert(tlist->source);
757         sp_parse(sp, n, wrd, tlist->source);
758
759         if (wrd->term_buf && wrd->term_len)
760         {
761             if (p->flagShowRecords)
762             {
763                 int i;
764                 printf("%*sIdx: [%s]", (level + 1) * 4, "",
765                        tlist->structure);
766                 printf("%s %s", tlist->index_name, tlist->source);
767                 printf(" XData:\"");
768                 for (i = 0; i<wrd->term_len && i < 40; i++)
769                     fputc(wrd->term_buf[i], stdout);
770                 fputc('"', stdout);
771                 if (wrd->term_len > 40)
772                     printf(" ...");
773                 fputc('\n', stdout);
774             }
775             else
776             {
777                 wrd->index_type = tlist->structure;
778                 wrd->index_name = tlist->index_name;
779                 (*p->tokenAdd)(wrd);
780             }
781         }
782     }
783 }
784
785 static int dumpkeys_r(struct source_parser *sp,
786                       data1_node *n, struct recExtractCtrl *p, int level,
787                       RecWord *wrd)
788 {
789     for (; n; n = n->next)
790     {
791         if (p->flagShowRecords) /* display element description to user */
792         {
793             if (n->which == DATA1N_root)
794             {
795                 printf("%*s", level * 4, "");
796                 printf("Record type: '%s'\n", n->u.root.type);
797             }
798             else if (n->which == DATA1N_tag)
799             {
800                 data1_element *e;
801
802                 printf("%*s", level * 4, "");
803                 if (!(e = n->u.tag.element))
804                     printf("Local tag: '%s'\n", n->u.tag.tag);
805                 else
806                 {
807                     printf("Elm: '%s' ", e->name);
808                     if (e->tag)
809                     {
810                         data1_tag *t = e->tag;
811
812                         printf("TagNam: '%s' ", t->names->name);
813                         printf("(");
814                         if (t->tagset)
815                             printf("%s[%d],", t->tagset->name, t->tagset->type);
816                         else
817                             printf("?,");
818                         if (t->which == DATA1T_numeric)
819                             printf("%d)", t->value.numeric);
820                         else
821                             printf("'%s')", t->value.string);
822                     }
823                     printf("\n");
824                 }
825             }
826         }
827
828         if (n->which == DATA1N_tag)
829         {
830             index_termlist(sp, n, n, p, level, wrd);
831             /* index start tag */
832             if (n->root->u.root.absyn)
833                 index_xpath(sp, n, p, level, wrd, ZEBRA_XPATH_ELM_BEGIN, 
834                             1 /* is start */);
835         }
836
837         if (n->child)
838             if (dumpkeys_r(sp, n->child, p, level + 1, wrd) < 0)
839                 return -1;
840
841
842         if (n->which == DATA1N_data)
843         {
844             data1_node *par = get_parent_tag(p->dh, n);
845
846             if (p->flagShowRecords)
847             {
848                 printf("%*s", level * 4, "");
849                 printf("Data: ");
850                 if (n->u.data.len > 256)
851                     printf("'%.170s ... %.70s'\n", n->u.data.data,
852                            n->u.data.data + n->u.data.len-70);
853                 else if (n->u.data.len > 0)
854                     printf("'%.*s'\n", n->u.data.len, n->u.data.data);
855                 else
856                     printf("NULL\n");
857             }
858
859             if (par)
860                 index_termlist(sp, par, n, p, level, wrd);
861
862             index_xpath(sp, n, p, level, wrd, ZEBRA_XPATH_CDATA, 
863                         0 /* is start */);
864         }
865
866         if (n->which == DATA1N_tag)
867         {
868             /* index end tag */
869             index_xpath(sp, n, p, level, wrd, ZEBRA_XPATH_ELM_END, 
870                         0 /* is start */);
871         }
872
873         if (p->flagShowRecords && n->which == DATA1N_root)
874         {
875             printf("%*s-------------\n\n", level * 4, "");
876         }
877     }
878     return 0;
879 }
880
881 static int dumpkeys(data1_node *n, struct recExtractCtrl *p, RecWord *wrd)
882 {
883     struct source_parser *sp = source_parser_create();
884     int r = dumpkeys_r(sp, n, p, 0, wrd);
885     source_parser_destroy(sp);
886     return r;
887 }
888
889 int grs_extract_tree(struct recExtractCtrl *p, data1_node *n)
890 {
891     RecWord wrd;
892
893     if (n->u.root.absyn && n->u.root.absyn->oid)
894         (*p->schemaAdd)(p, n->u.root.absyn->oid);
895     (*p->init)(p, &wrd);
896
897     /* data1_pr_tree(p->dh, n, stdout); */ 
898
899     return dumpkeys(n, p, &wrd);
900 }
901
902 static int grs_extract_sub(void *clientData, struct recExtractCtrl *p,
903                            NMEM mem,
904                            data1_node *(*grs_read)(struct grs_read_info *))
905 {
906     data1_node *n;
907     struct grs_read_info gri;
908     RecWord wrd;
909
910     gri.stream = p->stream;
911     gri.mem = mem;
912     gri.dh = p->dh;
913     gri.clientData = clientData;
914
915     n = (*grs_read)(&gri);
916     if (!n)
917         return RECCTRL_EXTRACT_EOF;
918     if (n->u.root.absyn && n->u.root.absyn->oid)
919         (*p->schemaAdd)(p, n->u.root.absyn->oid);
920     data1_concat_text(p->dh, mem, n);
921
922     /* ensure our data1 tree is UTF-8 */
923     data1_iconv(p->dh, mem, n, "UTF-8", data1_get_encoding(p->dh, n));
924
925
926     data1_remove_idzebra_subtree(p->dh, n);
927
928 #if 0
929     data1_pr_tree(p->dh, n, stdout);
930 #endif
931
932     (*p->init)(p, &wrd);
933     if (dumpkeys(n, p, &wrd) < 0)
934     {
935         return RECCTRL_EXTRACT_ERROR_GENERIC;
936     }
937     return RECCTRL_EXTRACT_OK;
938 }
939
940 int zebra_grs_extract(void *clientData, struct recExtractCtrl *p,
941                       data1_node *(*grs_read)(struct grs_read_info *))
942 {
943     int ret;
944     NMEM mem = nmem_create();
945     ret = grs_extract_sub(clientData, p, mem, grs_read);
946     nmem_destroy(mem);
947     return ret;
948 }
949
950 /*
951  * Return: -1: Nothing done. 0: Ok. >0: Bib-1 diagnostic.
952  */
953 static int process_comp(data1_handle dh, data1_node *n, Z_RecordComposition *c,
954                         char **addinfo, ODR o)
955 {
956     data1_esetname *eset;
957     Z_Espec1 *espec = 0;
958     Z_ElementSpec *p;
959
960     switch (c->which)
961     {
962     case Z_RecordComp_simple:
963         if (c->u.simple->which != Z_ElementSetNames_generic)
964             return 26; /* only generic form supported. Fix this later */
965         if (!(eset = data1_getesetbyname(dh, n->u.root.absyn,
966                                          c->u.simple->u.generic)))
967         {
968             yaz_log(YLOG_LOG, "Unknown esetname '%s'", c->u.simple->u.generic);
969             *addinfo = odr_strdup(o, c->u.simple->u.generic);
970             return 25; /* invalid esetname */
971         }
972         yaz_log(YLOG_DEBUG, "Esetname '%s' in simple compspec",
973              c->u.simple->u.generic);
974         espec = eset->spec;
975         break;
976     case Z_RecordComp_complex:
977         if (c->u.complex->generic)
978         {
979             /* insert check for schema */
980             if ((p = c->u.complex->generic->elementSpec))
981             {
982                 switch (p->which)
983                 {
984                 case Z_ElementSpec_elementSetName:
985                     if (!(eset =
986                           data1_getesetbyname(dh, n->u.root.absyn,
987                                               p->u.elementSetName)))
988                     {
989                         yaz_log(YLOG_DEBUG, "Unknown esetname '%s'",
990                              p->u.elementSetName);
991                         *addinfo = odr_strdup(o, p->u.elementSetName);
992                         return 25; /* invalid esetname */
993                     }
994                     yaz_log(YLOG_DEBUG, "Esetname '%s' in complex compspec",
995                          p->u.elementSetName);
996                     espec = eset->spec;
997                     break;
998                 case Z_ElementSpec_externalSpec:
999                     if (p->u.externalSpec->which == Z_External_espec1)
1000                     {
1001                         yaz_log(YLOG_DEBUG, "Got Espec-1");
1002                         espec = p->u.externalSpec-> u.espec1;
1003                     }
1004                     else
1005                     {
1006                         yaz_log(YLOG_LOG, "Unknown external espec.");
1007                         return 25; /* bad. what is proper diagnostic? */
1008                     }
1009                     break;
1010                 }
1011             }
1012         }
1013         else
1014             return 26; /* fix */
1015     }
1016     if (espec)
1017     {
1018         yaz_log(YLOG_DEBUG, "Element: Espec-1 match");
1019         return data1_doespec1(dh, n, espec);
1020     }
1021     else
1022     {
1023         yaz_log(YLOG_DEBUG, "Element: all match");
1024         return -1;
1025     }
1026 }
1027
1028 /* Add Zebra info in separate namespace ...
1029         <root 
1030          ...
1031          <metadata xmlns="http://www.indexdata.dk/zebra/">
1032           <size>359</size>
1033           <localnumber>447</localnumber>
1034           <filename>records/genera.xml</filename>
1035          </metadata>
1036         </root>
1037 */
1038
1039 static void zebra_xml_metadata(struct recRetrieveCtrl *p, data1_node *top,
1040                                 NMEM mem)
1041 {
1042     const char *idzebra_ns[3];
1043     const char *i2 = "\n  ";
1044     const char *i4 = "\n    ";
1045     data1_node *n;
1046
1047     idzebra_ns[0] = "xmlns";
1048     idzebra_ns[1] = "http://www.indexdata.dk/zebra/";
1049     idzebra_ns[2] = 0;
1050
1051     data1_mk_text(p->dh, mem, i2, top);
1052
1053     n = data1_mk_tag(p->dh, mem, "idzebra", idzebra_ns, top);
1054
1055     data1_mk_text(p->dh, mem, "\n", top);
1056
1057     data1_mk_text(p->dh, mem, i4, n);
1058     
1059     data1_mk_tag_data_int(p->dh, n, "size", p->recordSize, mem);
1060
1061     if (p->score != -1)
1062     {
1063         data1_mk_text(p->dh, mem, i4, n);
1064         data1_mk_tag_data_int(p->dh, n, "score", p->score, mem);
1065     }
1066     data1_mk_text(p->dh, mem, i4, n);
1067     data1_mk_tag_data_zint(p->dh, n, "localnumber", p->localno, mem);
1068     if (p->fname)
1069     {
1070         data1_mk_text(p->dh, mem, i4, n);
1071         data1_mk_tag_data_text(p->dh, n, "filename", p->fname, mem);
1072     }
1073     data1_mk_text(p->dh, mem, i2, n);
1074 }
1075
1076 int zebra_grs_retrieve(void *clientData, struct recRetrieveCtrl *p,
1077                        data1_node *(*grs_read)(struct grs_read_info *))
1078 {
1079     data1_node *node = 0, *onode = 0, *top;
1080     data1_node *dnew;
1081     data1_maptab *map;
1082     int res, selected = 0;
1083     NMEM mem;
1084     struct grs_read_info gri;
1085     const char *tagname;
1086
1087     const Odr_oid *requested_schema = 0;
1088     data1_marctab *marctab;
1089     int dummy;
1090     
1091     mem = nmem_create();
1092     gri.stream = p->stream;
1093     gri.mem = mem;
1094     gri.dh = p->dh;
1095     gri.clientData = clientData;
1096
1097     yaz_log(YLOG_DEBUG, "grs_retrieve");
1098     node = (*grs_read)(&gri);
1099     if (!node)
1100     {
1101         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1102         nmem_destroy(mem);
1103         return 0;
1104     }
1105     data1_concat_text(p->dh, mem, node);
1106
1107     data1_remove_idzebra_subtree(p->dh, node);
1108
1109 #if 0
1110     data1_pr_tree(p->dh, node, stdout);
1111 #endif
1112     top = data1_get_root_tag(p->dh, node);
1113
1114     yaz_log(YLOG_DEBUG, "grs_retrieve: size");
1115     tagname = data1_systag_lookup(node->u.root.absyn, "size", "size");
1116     if (tagname &&
1117         (dnew = data1_mk_tag_data_wd(p->dh, top, tagname, mem)))
1118     {
1119         dnew->u.data.what = DATA1I_text;
1120         dnew->u.data.data = dnew->lbuf;
1121         sprintf(dnew->u.data.data, "%d", p->recordSize);
1122         dnew->u.data.len = strlen(dnew->u.data.data);
1123     }
1124     
1125     tagname = data1_systag_lookup(node->u.root.absyn, "rank", "rank");
1126     if (tagname && p->score >= 0 &&
1127         (dnew = data1_mk_tag_data_wd(p->dh, top, tagname, mem)))
1128     {
1129         yaz_log(YLOG_DEBUG, "grs_retrieve: %s", tagname);
1130         dnew->u.data.what = DATA1I_num;
1131         dnew->u.data.data = dnew->lbuf;
1132         sprintf(dnew->u.data.data, "%d", p->score);
1133         dnew->u.data.len = strlen(dnew->u.data.data);
1134     }
1135
1136     tagname = data1_systag_lookup(node->u.root.absyn, "sysno",
1137                                   "localControlNumber");
1138     if (tagname && p->localno > 0 &&
1139         (dnew = data1_mk_tag_data_wd(p->dh, top, tagname, mem)))
1140     {
1141         yaz_log(YLOG_DEBUG, "grs_retrieve: %s", tagname);
1142         dnew->u.data.what = DATA1I_text;
1143         dnew->u.data.data = dnew->lbuf;
1144         
1145         sprintf(dnew->u.data.data, ZINT_FORMAT, p->localno);
1146         dnew->u.data.len = strlen(dnew->u.data.data);
1147     }
1148
1149     if (!p->input_format)
1150     {  /* SUTRS is default input_format */
1151         p->input_format = yaz_oid_recsyn_sutrs;
1152     }
1153     assert(p->input_format);
1154
1155     if (!oid_oidcmp(p->input_format, yaz_oid_recsyn_xml))
1156         zebra_xml_metadata(p, top, mem);
1157
1158 #if 0
1159     data1_pr_tree(p->dh, node, stdout);
1160 #endif
1161     if (p->comp && p->comp->which == Z_RecordComp_complex &&
1162         p->comp->u.complex->generic &&
1163         p->comp->u.complex->generic->which == Z_Schema_oid &&
1164         p->comp->u.complex->generic->schema.oid)
1165     {
1166         requested_schema = p->comp->u.complex->generic->schema.oid;
1167     }
1168     /* If schema has been specified, map if possible, then check that
1169      * we got the right one 
1170      */
1171     if (requested_schema)
1172     {
1173         yaz_log(YLOG_DEBUG, "grs_retrieve: schema mapping");
1174         for (map = node->u.root.absyn->maptabs; map; map = map->next)
1175         {
1176             if (!oid_oidcmp(map->oid, requested_schema))
1177             {
1178                 onode = node;
1179                 if (!(node = data1_map_record(p->dh, onode, map, mem)))
1180                 {
1181                     p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1182                     nmem_destroy(mem);
1183                     return 0;
1184                 }
1185                 break;
1186             }
1187         }
1188         if (node->u.root.absyn 
1189             && oid_oidcmp(requested_schema, node->u.root.absyn->oid))
1190         {
1191             p->diagnostic = YAZ_BIB1_RECORD_NOT_AVAILABLE_IN_REQUESTED_SYNTAX;
1192             nmem_destroy(mem);
1193             return 0;
1194         }
1195     }
1196     /*
1197      * Does the requested format match a known syntax-mapping? (this reflects
1198      * the overlap of schema and formatting which is inherent in the MARC
1199      * family)
1200      */
1201     yaz_log(YLOG_DEBUG, "grs_retrieve: syntax mapping");
1202     if (node->u.root.absyn)
1203         for (map = node->u.root.absyn->maptabs; map; map = map->next)
1204         {
1205             if (!oid_oidcmp(map->oid, p->input_format))
1206             {
1207                 onode = node;
1208                 if (!(node = data1_map_record(p->dh, onode, map, mem)))
1209                 {
1210                     p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1211                     nmem_destroy(mem);
1212                     return 0;
1213                 }
1214                 break;
1215             }
1216         }
1217     yaz_log(YLOG_DEBUG, "grs_retrieve: schemaIdentifier");
1218     if (node->u.root.absyn && node->u.root.absyn->oid 
1219         && !oid_oidcmp(p->input_format, yaz_oid_recsyn_grs_1))
1220     {
1221         char oid_str[OID_STR_MAX];
1222         char *dot_str = oid_oid_to_dotstring(node->u.root.absyn->oid, oid_str);
1223         
1224         if (dot_str && (dnew = data1_mk_tag_data_wd(p->dh, top, 
1225                                                     "schemaIdentifier", mem)))
1226         {
1227             dnew->u.data.what = DATA1I_oid;
1228             dnew->u.data.data = (char *) nmem_strdup(mem, dot_str);
1229             dnew->u.data.len = strlen(dot_str);
1230         }
1231     }
1232
1233     yaz_log(YLOG_DEBUG, "grs_retrieve: element spec");
1234     if (p->comp && (res = process_comp(p->dh, node, p->comp, &p->addinfo,
1235                                        p->odr)) > 0)
1236     {
1237         p->diagnostic = res;
1238         nmem_destroy(mem);
1239         return 0;
1240     }
1241     else if (p->comp && !res)
1242         selected = 1;
1243
1244 #if 0
1245     data1_pr_tree(p->dh, node, stdout);
1246 #endif
1247     yaz_log(YLOG_DEBUG, "grs_retrieve: transfer syntax mapping");
1248
1249     p->output_format = p->input_format;
1250
1251     assert(p->input_format);
1252     if (!oid_oidcmp(p->input_format, yaz_oid_recsyn_xml))
1253     {
1254 #if 0
1255         data1_pr_tree(p->dh, node, stdout);
1256 #endif
1257         /* default output encoding for XML is UTF-8 */
1258         data1_iconv(p->dh, mem, node,
1259                      p->encoding ? p->encoding : "UTF-8",
1260                      data1_get_encoding(p->dh, node));
1261
1262         if (!(p->rec_buf = data1_nodetoidsgml(p->dh, node, selected,
1263                                               &p->rec_len)))
1264             p->diagnostic = YAZ_BIB1_RECORD_NOT_AVAILABLE_IN_REQUESTED_SYNTAX;
1265         else
1266         {
1267             char *new_buf = (char*) odr_malloc(p->odr, p->rec_len);
1268             memcpy(new_buf, p->rec_buf, p->rec_len);
1269             p->rec_buf = new_buf;
1270         }
1271     }
1272     else if (!oid_oidcmp(p->input_format, yaz_oid_recsyn_grs_1))
1273     {
1274         data1_iconv(p->dh, mem, node, "UTF-8", data1_get_encoding(p->dh, node));
1275         dummy = 0;
1276         if (!(p->rec_buf = data1_nodetogr(p->dh, node, selected,
1277                                           p->odr, &dummy)))
1278             p->diagnostic = YAZ_BIB1_RECORD_NOT_AVAILABLE_IN_REQUESTED_SYNTAX;
1279         else
1280             p->rec_len = -1;
1281     }
1282     else if (!oid_oidcmp(p->input_format, yaz_oid_recsyn_explain))
1283     {
1284         /* ensure our data1 tree is UTF-8 */
1285         data1_iconv(p->dh, mem, node, "UTF-8", data1_get_encoding(p->dh, node));
1286         
1287         if (!(p->rec_buf = data1_nodetoexplain(p->dh, node, selected,
1288                                                p->odr)))
1289             p->diagnostic = YAZ_BIB1_RECORD_NOT_AVAILABLE_IN_REQUESTED_SYNTAX;
1290         else
1291             p->rec_len = -1;
1292     }
1293     else if (!oid_oidcmp(p->input_format, yaz_oid_recsyn_summary))
1294     {
1295         /* ensure our data1 tree is UTF-8 */
1296         data1_iconv(p->dh, mem, node, "UTF-8", data1_get_encoding(p->dh, node));
1297         if (!(p->rec_buf = data1_nodetosummary(p->dh, node, selected,
1298                                                p->odr)))
1299             p->diagnostic = YAZ_BIB1_RECORD_NOT_AVAILABLE_IN_REQUESTED_SYNTAX;
1300         else
1301             p->rec_len = -1;
1302     }
1303     else if (!oid_oidcmp(p->input_format, yaz_oid_recsyn_sutrs))
1304     {
1305         if (p->encoding)
1306             data1_iconv(p->dh, mem, node, p->encoding,
1307                          data1_get_encoding(p->dh, node));
1308         if (!(p->rec_buf = data1_nodetobuf(p->dh, node, selected,
1309                                            &p->rec_len)))
1310             p->diagnostic = YAZ_BIB1_RECORD_NOT_AVAILABLE_IN_REQUESTED_SYNTAX;
1311         else
1312         {
1313             char *new_buf = (char*) odr_malloc(p->odr, p->rec_len);
1314             memcpy(new_buf, p->rec_buf, p->rec_len);
1315             p->rec_buf = new_buf;
1316         }
1317     }
1318     else if (!oid_oidcmp(p->input_format, yaz_oid_recsyn_soif))
1319     {
1320         if (p->encoding)
1321             data1_iconv(p->dh, mem, node, p->encoding,
1322                          data1_get_encoding(p->dh, node));
1323         if (!(p->rec_buf = data1_nodetosoif(p->dh, node, selected,
1324                                             &p->rec_len)))
1325             p->diagnostic = YAZ_BIB1_RECORD_NOT_AVAILABLE_IN_REQUESTED_SYNTAX;
1326         else
1327         {
1328             char *new_buf = (char*) odr_malloc(p->odr, p->rec_len);
1329             memcpy(new_buf, p->rec_buf, p->rec_len);
1330             p->rec_buf = new_buf;
1331         }
1332     }
1333     else
1334     {
1335         if (!node->u.root.absyn)
1336             p->diagnostic = YAZ_BIB1_RECORD_NOT_AVAILABLE_IN_REQUESTED_SYNTAX;
1337         else
1338         {
1339             for (marctab = node->u.root.absyn->marc; marctab;
1340                  marctab = marctab->next)
1341                 if (marctab->oid && !oid_oidcmp(marctab->oid, p->input_format))
1342                     break;
1343             if (!marctab)
1344                 p->diagnostic = YAZ_BIB1_RECORD_NOT_AVAILABLE_IN_REQUESTED_SYNTAX;
1345             else
1346             {
1347                 if (p->encoding)
1348                     data1_iconv(p->dh, mem, node, p->encoding,
1349                                  data1_get_encoding(p->dh, node));
1350                 if (!(p->rec_buf = data1_nodetomarc(p->dh, marctab, node,
1351                                                     selected, &p->rec_len)))
1352                     p->diagnostic = YAZ_BIB1_RECORD_NOT_AVAILABLE_IN_REQUESTED_SYNTAX;
1353                 else
1354                 {
1355                     char *new_buf = (char*) odr_malloc(p->odr, p->rec_len);
1356                     memcpy(new_buf, p->rec_buf, p->rec_len);
1357                     p->rec_buf = new_buf;
1358                 }
1359             }
1360         }
1361     }
1362     nmem_destroy(mem);
1363     return 0;
1364 }
1365
1366 /*
1367  * Local variables:
1368  * c-basic-offset: 4
1369  * c-file-style: "Stroustrup"
1370  * indent-tabs-mode: nil
1371  * End:
1372  * vim: shiftwidth=4 tabstop=8 expandtab
1373  */
1374