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