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