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