For searches, allow truncmax value to be controlled with attribute 13.
[idzebra-moved-to-github.git] / index / recgrs.c
1 /* $Id: recgrs.c,v 1.13 2007-01-15 15:10:17 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_staticrank(struct recExtractCtrl *p,
529                              RecWord *wrd,
530                              data1_absyn *absyn)
531 {
532     const char *staticrank_index = data1_absyn_get_staticrank(absyn);
533
534     if (staticrank_index && !strcmp(wrd->index_name, staticrank_index))
535     {
536         char valz[20];
537         size_t len = wrd->term_len;
538
539         if (len > sizeof(valz)-1)
540             len = sizeof(valz)-1;
541         memcpy(valz, wrd->term_buf, len);
542         valz[len] = '\0';
543         p->staticrank = atozint(valz);
544     }
545 }
546
547 static void index_xpath(struct source_parser *sp, data1_node *n,
548                         struct recExtractCtrl *p,
549                         int level, RecWord *wrd,
550                         char *xpath_index,
551                         int xpath_is_start
552     )
553 {
554     int i;
555     char tag_path_full[1024];
556     int termlist_only = 1;
557     data1_termlist *tl;
558     int xpdone = 0;
559
560     if (!n->root->u.root.absyn 
561         || 
562         n->root->u.root.absyn->xpath_indexing == DATA1_XPATH_INDEXING_ENABLE)
563     {
564         termlist_only = 0;
565     }
566
567
568     switch (n->which)
569     {
570     case DATA1N_data:
571         wrd->term_buf = n->u.data.data;
572         wrd->term_len = n->u.data.len;
573         xpdone = 0;
574
575         mk_tag_path_full(tag_path_full, sizeof(tag_path_full), n);
576         
577         /* If we have a matching termlist... */
578         if (n->root->u.root.absyn && 
579             (tl = xpath_termlist_by_tagpath(tag_path_full, n)))
580         {
581             zint max_seqno = 0;
582             for (; tl; tl = tl->next)
583             {
584                 /* need to copy recword because it may be changed */
585                 RecWord wrd_tl;
586                 wrd->index_type = *tl->structure;
587                 memcpy (&wrd_tl, wrd, sizeof(*wrd));
588                 if (tl->source)
589                     sp_parse(sp, n, &wrd_tl, tl->source);
590                 
591                 /* this is just the old fashioned attribute based index */
592                 wrd_tl.index_name = tl->index_name;
593                 if (p->flagShowRecords)
594                 {
595                     int i;
596                     printf("%*sIdx: [%s]", (level + 1) * 4, "",
597                            tl->structure);
598                     printf("%s %s", tl->index_name, tl->source);
599                     printf (" XData:\"");
600                     for (i = 0; i<wrd_tl.term_len && i < 40; i++)
601                         fputc (wrd_tl.term_buf[i], stdout);
602                     fputc ('"', stdout);
603                     if (wrd_tl.term_len > 40)
604                         printf (" ...");
605                     fputc ('\n', stdout);
606                 }
607                 else
608                 {
609                     (*p->tokenAdd)(&wrd_tl);
610                     index_staticrank(p, &wrd_tl, n->root->u.root.absyn);
611                 }
612                 if (wrd_tl.seqno > max_seqno)
613                     max_seqno = wrd_tl.seqno;
614             }
615             if (max_seqno)
616                 wrd->seqno = max_seqno;
617                 
618         }
619         /* xpath indexing is done, if there was no termlist given, 
620            or no ! in the termlist, and default indexing is enabled... */
621         if (!p->flagShowRecords && !xpdone && !termlist_only)
622         {
623             wrd->index_name = xpath_index;
624             wrd->index_type = 'w';
625             (*p->tokenAdd)(wrd);
626         }
627         break;
628     case DATA1N_tag:
629         if (termlist_only)
630             return;
631         mk_tag_path_full(tag_path_full, sizeof(tag_path_full), n);
632
633         wrd->index_type = '0';
634         wrd->term_buf = tag_path_full;
635         wrd->term_len = strlen(tag_path_full);
636         wrd->index_name = xpath_index;
637         if (p->flagShowRecords)
638         {
639             printf("%*s tag=", (level + 1) * 4, "");
640             for (i = 0; i<wrd->term_len && i < 40; i++)
641                 fputc (wrd->term_buf[i], stdout);
642             if (i == 40)
643                 printf (" ..");
644             printf("\n");
645         }
646         else
647         {
648             data1_xattr *xp;
649
650             (*p->tokenAdd)(wrd);   /* index element pag (AKA tag path) */
651             
652             if (xpath_is_start == 1) /* only for the starting tag... */
653             {
654 #define MAX_ATTR_COUNT 50
655                 data1_termlist *tll[MAX_ATTR_COUNT];
656                 
657                 int i = 0;
658                 for (xp = n->u.tag.attributes; xp; xp = xp->next) {
659                     char comb[512];
660                     char attr_tag_path_full[1024]; 
661                     
662                     /* this could be cached as well */
663                     sprintf (attr_tag_path_full, "@%s/%s",
664                              xp->name, tag_path_full);
665                     
666                     tll[i] = xpath_termlist_by_tagpath(attr_tag_path_full,n);
667                     
668                     /* attribute  (no value) */
669                     wrd->index_type = '0';
670                     wrd->index_name = ZEBRA_XPATH_ATTR_NAME;
671                     wrd->term_buf = xp->name;
672                     wrd->term_len = strlen(xp->name);
673                     
674                     wrd->seqno--;
675                     (*p->tokenAdd)(wrd);
676                     
677                     if (xp->value 
678                         &&
679                         strlen(xp->name) + strlen(xp->value) < sizeof(comb)-2)
680                     {
681                         /* attribute value exact */
682                         strcpy (comb, xp->name);
683                         strcat (comb, "=");
684                         strcat (comb, xp->value);
685                         
686                         wrd->index_name = ZEBRA_XPATH_ATTR_NAME;
687                         wrd->index_type = '0';
688                         wrd->term_buf = comb;
689                         wrd->term_len = strlen(comb);
690                         wrd->seqno--;
691                         
692                         (*p->tokenAdd)(wrd);
693                     }                
694                     i++;
695                 }
696                 
697                 i = 0;
698                 for (xp = n->u.tag.attributes; xp; xp = xp->next) {
699                     data1_termlist *tl;
700                     char attr_tag_path_full[1024];
701                     int xpdone = 0;
702                     
703                     sprintf (attr_tag_path_full, "@%s/%s",
704                              xp->name, tag_path_full);
705                     
706                     if ((tl = tll[i]))
707                     {
708                         /* If there is a termlist given (=xelm directive) */
709                         for (; tl; tl = tl->next)
710                         {
711                             if (!tl->index_name)
712                             {
713                                 /* add xpath index for the attribute */
714                                 index_xpath_attr (attr_tag_path_full, xp->name,
715                                                   xp->value, tl->structure,
716                                                   p, wrd);
717                                 xpdone = 1;
718                             } else {
719                                 /* index attribute value (only path/@attr) */
720                                 if (xp->value) 
721                                 {
722                                     wrd->index_name = tl->index_name;
723                                     wrd->index_type = *tl->structure;
724                                     wrd->term_buf = xp->value;
725                                     wrd->term_len = strlen(xp->value);
726                                     (*p->tokenAdd)(wrd);
727                                     index_staticrank(p, wrd,
728                                                      n->root->u.root.absyn);
729                                 }
730                             }
731                         }
732                     }
733                     /* if there was no termlist for the given path, 
734                        or the termlist didn't have a ! element, index 
735                        the attribute as "w" */
736                     if ((!xpdone) && (!termlist_only))
737                     {
738                         index_xpath_attr (attr_tag_path_full, xp->name,
739                                           xp->value,  "w", p, wrd);
740                     }
741                     i++;
742                 }
743             }
744         }
745     }
746 }
747
748 static void index_termlist (struct source_parser *sp, data1_node *par,
749                             data1_node *n,
750                             struct recExtractCtrl *p, int level, RecWord *wrd)
751 {
752     data1_termlist *tlist = 0;
753     data1_datatype dtype = DATA1K_string;
754
755     /*
756      * cycle up towards the root until we find a tag with an att..
757      * this has the effect of indexing locally defined tags with
758      * the attribute of their ancestor in the record.
759      */
760     
761     while (!par->u.tag.element)
762         if (!par->parent || !(par=get_parent_tag(p->dh, par->parent)))
763             break;
764     if (!par || !(tlist = par->u.tag.element->termlists))
765         return;
766     if (par->u.tag.element->tag)
767         dtype = par->u.tag.element->tag->kind;
768
769     for (; tlist; tlist = tlist->next)
770     {
771         /* consider source */
772         wrd->term_buf = 0;
773         assert(tlist->source);
774         sp_parse(sp, n, wrd, tlist->source);
775
776         if (wrd->term_buf && wrd->term_len)
777         {
778             if (p->flagShowRecords)
779             {
780                 int i;
781                 printf("%*sIdx: [%s]", (level + 1) * 4, "",
782                        tlist->structure);
783                 printf("%s %s", tlist->index_name, tlist->source);
784                 printf (" XData:\"");
785                 for (i = 0; i<wrd->term_len && i < 40; i++)
786                     fputc (wrd->term_buf[i], stdout);
787                 fputc ('"', stdout);
788                 if (wrd->term_len > 40)
789                     printf (" ...");
790                 fputc ('\n', stdout);
791             }
792             else
793             {
794                 wrd->index_type = *tlist->structure;
795                 wrd->index_name = tlist->index_name;
796                 index_staticrank(p, wrd, n->root->u.root.absyn);
797                 (*p->tokenAdd)(wrd);
798             }
799         }
800     }
801 }
802
803 static int dumpkeys_r(struct source_parser *sp,
804                       data1_node *n, struct recExtractCtrl *p, int level,
805                       RecWord *wrd)
806 {
807     for (; n; n = n->next)
808     {
809         if (p->flagShowRecords) /* display element description to user */
810         {
811             if (n->which == DATA1N_root)
812             {
813                 printf("%*s", level * 4, "");
814                 printf("Record type: '%s'\n", n->u.root.type);
815             }
816             else if (n->which == DATA1N_tag)
817             {
818                 data1_element *e;
819
820                 printf("%*s", level * 4, "");
821                 if (!(e = n->u.tag.element))
822                     printf("Local tag: '%s'\n", n->u.tag.tag);
823                 else
824                 {
825                     printf("Elm: '%s' ", e->name);
826                     if (e->tag)
827                     {
828                         data1_tag *t = e->tag;
829
830                         printf("TagNam: '%s' ", t->names->name);
831                         printf("(");
832                         if (t->tagset)
833                             printf("%s[%d],", t->tagset->name, t->tagset->type);
834                         else
835                             printf("?,");
836                         if (t->which == DATA1T_numeric)
837                             printf("%d)", t->value.numeric);
838                         else
839                             printf("'%s')", t->value.string);
840                     }
841                     printf("\n");
842                 }
843             }
844         }
845
846         if (n->which == DATA1N_tag)
847         {
848             index_termlist(sp, n, n, p, level, wrd);
849             /* index start tag */
850             if (n->root->u.root.absyn)
851                 index_xpath(sp, n, p, level, wrd, ZEBRA_XPATH_ELM_BEGIN, 
852                             1 /* is start */);
853         }
854
855         if (n->child)
856             if (dumpkeys_r(sp, n->child, p, level + 1, wrd) < 0)
857                 return -1;
858
859
860         if (n->which == DATA1N_data)
861         {
862             data1_node *par = get_parent_tag(p->dh, n);
863
864             if (p->flagShowRecords)
865             {
866                 printf("%*s", level * 4, "");
867                 printf("Data: ");
868                 if (n->u.data.len > 256)
869                     printf("'%.170s ... %.70s'\n", n->u.data.data,
870                            n->u.data.data + n->u.data.len-70);
871                 else if (n->u.data.len > 0)
872                     printf("'%.*s'\n", n->u.data.len, n->u.data.data);
873                 else
874                     printf("NULL\n");
875             }
876
877             if (par)
878                 index_termlist(sp, par, n, p, level, wrd);
879
880             index_xpath(sp, n, p, level, wrd, ZEBRA_XPATH_CDATA, 
881                         0 /* is start */);
882         }
883
884         if (n->which == DATA1N_tag)
885         {
886             /* index end tag */
887             index_xpath(sp, n, p, level, wrd, ZEBRA_XPATH_ELM_END, 
888                         0 /* is start */);
889         }
890
891         if (p->flagShowRecords && n->which == DATA1N_root)
892         {
893             printf("%*s-------------\n\n", level * 4, "");
894         }
895     }
896     return 0;
897 }
898
899 static int dumpkeys(data1_node *n, struct recExtractCtrl *p, RecWord *wrd)
900 {
901     struct source_parser *sp = source_parser_create();
902     int r = dumpkeys_r(sp, n, p, 0, wrd);
903     source_parser_destroy(sp);
904     return r;
905 }
906
907 int grs_extract_tree(struct recExtractCtrl *p, data1_node *n)
908 {
909     oident oe;
910     int oidtmp[OID_SIZE];
911     RecWord wrd;
912
913     oe.proto = PROTO_Z3950;
914     oe.oclass = CLASS_SCHEMA;
915     if (n->u.root.absyn)
916     {
917         oe.value = n->u.root.absyn->reference;
918         
919         if ((oid_ent_to_oid (&oe, oidtmp)))
920             (*p->schemaAdd)(p, oidtmp);
921     }
922     (*p->init)(p, &wrd);
923
924     /* data1_pr_tree(p->dh, n, stdout); */ 
925
926     return dumpkeys(n, p, &wrd);
927 }
928
929 static int grs_extract_sub(void *clientData, struct recExtractCtrl *p,
930                            NMEM mem,
931                            data1_node *(*grs_read)(struct grs_read_info *))
932 {
933     data1_node *n;
934     struct grs_read_info gri;
935     oident oe;
936     int oidtmp[OID_SIZE];
937     RecWord wrd;
938
939     gri.stream = p->stream;
940     gri.mem = mem;
941     gri.dh = p->dh;
942     gri.clientData = clientData;
943
944     n = (*grs_read)(&gri);
945     if (!n)
946         return RECCTRL_EXTRACT_EOF;
947     oe.proto = PROTO_Z3950;
948     oe.oclass = CLASS_SCHEMA;
949 #if 0
950     if (!n->u.root.absyn)
951         return RECCTRL_EXTRACT_ERROR;
952 #endif
953     if (n->u.root.absyn)
954     {
955         oe.value = n->u.root.absyn->reference;
956         if ((oid_ent_to_oid (&oe, oidtmp)))
957             (*p->schemaAdd)(p, oidtmp);
958     }
959     data1_concat_text(p->dh, mem, n);
960
961     /* ensure our data1 tree is UTF-8 */
962     data1_iconv (p->dh, mem, n, "UTF-8", data1_get_encoding(p->dh, n));
963
964
965     data1_remove_idzebra_subtree (p->dh, n);
966
967 #if 0
968     data1_pr_tree (p->dh, n, stdout);
969 #endif
970
971     (*p->init)(p, &wrd);
972     if (dumpkeys(n, p, &wrd) < 0)
973     {
974         return RECCTRL_EXTRACT_ERROR_GENERIC;
975     }
976     return RECCTRL_EXTRACT_OK;
977 }
978
979 int zebra_grs_extract(void *clientData, struct recExtractCtrl *p,
980                       data1_node *(*grs_read)(struct grs_read_info *))
981 {
982     int ret;
983     NMEM mem = nmem_create ();
984     ret = grs_extract_sub(clientData, p, mem, grs_read);
985     nmem_destroy(mem);
986     return ret;
987 }
988
989 /*
990  * Return: -1: Nothing done. 0: Ok. >0: Bib-1 diagnostic.
991  */
992 static int process_comp(data1_handle dh, data1_node *n, Z_RecordComposition *c,
993                         char **addinfo, ODR o)
994 {
995     data1_esetname *eset;
996     Z_Espec1 *espec = 0;
997     Z_ElementSpec *p;
998
999     switch (c->which)
1000     {
1001     case Z_RecordComp_simple:
1002         if (c->u.simple->which != Z_ElementSetNames_generic)
1003             return 26; /* only generic form supported. Fix this later */
1004         if (!(eset = data1_getesetbyname(dh, n->u.root.absyn,
1005                                          c->u.simple->u.generic)))
1006         {
1007             yaz_log(YLOG_LOG, "Unknown esetname '%s'", c->u.simple->u.generic);
1008             *addinfo = odr_strdup(o, c->u.simple->u.generic);
1009             return 25; /* invalid esetname */
1010         }
1011         yaz_log(YLOG_DEBUG, "Esetname '%s' in simple compspec",
1012              c->u.simple->u.generic);
1013         espec = eset->spec;
1014         break;
1015     case Z_RecordComp_complex:
1016         if (c->u.complex->generic)
1017         {
1018             /* insert check for schema */
1019             if ((p = c->u.complex->generic->elementSpec))
1020             {
1021                 switch (p->which)
1022                 {
1023                 case Z_ElementSpec_elementSetName:
1024                     if (!(eset =
1025                           data1_getesetbyname(dh, n->u.root.absyn,
1026                                               p->u.elementSetName)))
1027                     {
1028                         yaz_log(YLOG_DEBUG, "Unknown esetname '%s'",
1029                              p->u.elementSetName);
1030                         *addinfo = odr_strdup(o, p->u.elementSetName);
1031                         return 25; /* invalid esetname */
1032                     }
1033                     yaz_log(YLOG_DEBUG, "Esetname '%s' in complex compspec",
1034                          p->u.elementSetName);
1035                     espec = eset->spec;
1036                     break;
1037                 case Z_ElementSpec_externalSpec:
1038                     if (p->u.externalSpec->which == Z_External_espec1)
1039                     {
1040                         yaz_log(YLOG_DEBUG, "Got Espec-1");
1041                         espec = p->u.externalSpec-> u.espec1;
1042                     }
1043                     else
1044                     {
1045                         yaz_log(YLOG_LOG, "Unknown external espec.");
1046                         return 25; /* bad. what is proper diagnostic? */
1047                     }
1048                     break;
1049                 }
1050             }
1051         }
1052         else
1053             return 26; /* fix */
1054     }
1055     if (espec)
1056     {
1057         yaz_log(YLOG_DEBUG, "Element: Espec-1 match");
1058         return data1_doespec1(dh, n, espec);
1059     }
1060     else
1061     {
1062         yaz_log(YLOG_DEBUG, "Element: all match");
1063         return -1;
1064     }
1065 }
1066
1067 /* Add Zebra info in separate namespace ...
1068         <root 
1069          ...
1070          <metadata xmlns="http://www.indexdata.dk/zebra/">
1071           <size>359</size>
1072           <localnumber>447</localnumber>
1073           <filename>records/genera.xml</filename>
1074          </metadata>
1075         </root>
1076 */
1077
1078 static void zebra_xml_metadata (struct recRetrieveCtrl *p, data1_node *top,
1079                                 NMEM mem)
1080 {
1081     const char *idzebra_ns[3];
1082     const char *i2 = "\n  ";
1083     const char *i4 = "\n    ";
1084     data1_node *n;
1085
1086     idzebra_ns[0] = "xmlns";
1087     idzebra_ns[1] = "http://www.indexdata.dk/zebra/";
1088     idzebra_ns[2] = 0;
1089
1090     data1_mk_text (p->dh, mem, i2, top);
1091
1092     n = data1_mk_tag (p->dh, mem, "idzebra", idzebra_ns, top);
1093
1094     data1_mk_text (p->dh, mem, "\n", top);
1095
1096     data1_mk_text (p->dh, mem, i4, n);
1097     
1098     data1_mk_tag_data_int (p->dh, n, "size", p->recordSize, mem);
1099
1100     if (p->score != -1)
1101     {
1102         data1_mk_text (p->dh, mem, i4, n);
1103         data1_mk_tag_data_int (p->dh, n, "score", p->score, mem);
1104     }
1105     data1_mk_text (p->dh, mem, i4, n);
1106     data1_mk_tag_data_zint (p->dh, n, "localnumber", p->localno, mem);
1107     if (p->fname)
1108     {
1109         data1_mk_text (p->dh, mem, i4, n);
1110         data1_mk_tag_data_text(p->dh, n, "filename", p->fname, mem);
1111     }
1112     data1_mk_text (p->dh, mem, i2, n);
1113 }
1114
1115 int zebra_grs_retrieve(void *clientData, struct recRetrieveCtrl *p,
1116                        data1_node *(*grs_read)(struct grs_read_info *))
1117 {
1118     data1_node *node = 0, *onode = 0, *top;
1119     data1_node *dnew;
1120     data1_maptab *map;
1121     int res, selected = 0;
1122     NMEM mem;
1123     struct grs_read_info gri;
1124     const char *tagname;
1125
1126     int requested_schema = VAL_NONE;
1127     data1_marctab *marctab;
1128     int dummy;
1129     
1130     mem = nmem_create();
1131     gri.stream = p->stream;
1132     gri.mem = mem;
1133     gri.dh = p->dh;
1134     gri.clientData = clientData;
1135
1136     yaz_log(YLOG_DEBUG, "grs_retrieve");
1137     node = (*grs_read)(&gri);
1138     if (!node)
1139     {
1140         p->diagnostic = 14;
1141         nmem_destroy (mem);
1142         return 0;
1143     }
1144     data1_concat_text(p->dh, mem, node);
1145
1146     data1_remove_idzebra_subtree (p->dh, node);
1147
1148 #if 0
1149     data1_pr_tree (p->dh, node, stdout);
1150 #endif
1151     top = data1_get_root_tag (p->dh, node);
1152
1153     yaz_log(YLOG_DEBUG, "grs_retrieve: size");
1154     tagname = data1_systag_lookup(node->u.root.absyn, "size", "size");
1155     if (tagname &&
1156         (dnew = data1_mk_tag_data_wd(p->dh, top, tagname, mem)))
1157     {
1158         dnew->u.data.what = DATA1I_text;
1159         dnew->u.data.data = dnew->lbuf;
1160         sprintf(dnew->u.data.data, "%d", p->recordSize);
1161         dnew->u.data.len = strlen(dnew->u.data.data);
1162     }
1163     
1164     tagname = data1_systag_lookup(node->u.root.absyn, "rank", "rank");
1165     if (tagname && p->score >= 0 &&
1166         (dnew = data1_mk_tag_data_wd(p->dh, top, tagname, mem)))
1167     {
1168         yaz_log(YLOG_DEBUG, "grs_retrieve: %s", tagname);
1169         dnew->u.data.what = DATA1I_num;
1170         dnew->u.data.data = dnew->lbuf;
1171         sprintf(dnew->u.data.data, "%d", p->score);
1172         dnew->u.data.len = strlen(dnew->u.data.data);
1173     }
1174
1175     tagname = data1_systag_lookup(node->u.root.absyn, "sysno",
1176                                   "localControlNumber");
1177     if (tagname && p->localno > 0 &&
1178         (dnew = data1_mk_tag_data_wd(p->dh, top, tagname, mem)))
1179     {
1180         yaz_log(YLOG_DEBUG, "grs_retrieve: %s", tagname);
1181         dnew->u.data.what = DATA1I_text;
1182         dnew->u.data.data = dnew->lbuf;
1183         
1184         sprintf(dnew->u.data.data, ZINT_FORMAT, p->localno);
1185         dnew->u.data.len = strlen(dnew->u.data.data);
1186     }
1187
1188     if (p->input_format == VAL_TEXT_XML)
1189        zebra_xml_metadata (p, top, mem);
1190
1191 #if 0
1192     data1_pr_tree (p->dh, node, stdout);
1193 #endif
1194     if (p->comp && p->comp->which == Z_RecordComp_complex &&
1195         p->comp->u.complex->generic &&
1196         p->comp->u.complex->generic->which == Z_Schema_oid &&
1197         p->comp->u.complex->generic->schema.oid)
1198     {
1199         oident *oe = oid_getentbyoid (p->comp->u.complex->generic->schema.oid);
1200         if (oe)
1201             requested_schema = oe->value;
1202     }
1203     /* If schema has been specified, map if possible, then check that
1204      * we got the right one 
1205      */
1206     if (requested_schema != VAL_NONE)
1207     {
1208         yaz_log(YLOG_DEBUG, "grs_retrieve: schema mapping");
1209         for (map = node->u.root.absyn->maptabs; map; map = map->next)
1210         {
1211             if (map->target_absyn_ref == requested_schema)
1212             {
1213                 onode = node;
1214                 if (!(node = data1_map_record(p->dh, onode, map, mem)))
1215                 {
1216                     p->diagnostic = 14;
1217                     nmem_destroy (mem);
1218                     return 0;
1219                 }
1220                 break;
1221             }
1222         }
1223         if (node->u.root.absyn &&
1224             requested_schema != node->u.root.absyn->reference)
1225         {
1226             p->diagnostic = 238;
1227             nmem_destroy (mem);
1228             return 0;
1229         }
1230     }
1231     /*
1232      * Does the requested format match a known syntax-mapping? (this reflects
1233      * the overlap of schema and formatting which is inherent in the MARC
1234      * family)
1235      */
1236     yaz_log(YLOG_DEBUG, "grs_retrieve: syntax mapping");
1237     if (node->u.root.absyn)
1238         for (map = node->u.root.absyn->maptabs; map; map = map->next)
1239         {
1240             if (map->target_absyn_ref == p->input_format)
1241             {
1242                 onode = node;
1243                 if (!(node = data1_map_record(p->dh, onode, map, mem)))
1244                 {
1245                     p->diagnostic = 14;
1246                     nmem_destroy (mem);
1247                     return 0;
1248                 }
1249                 break;
1250             }
1251         }
1252     yaz_log(YLOG_DEBUG, "grs_retrieve: schemaIdentifier");
1253     if (node->u.root.absyn &&
1254         node->u.root.absyn->reference != VAL_NONE &&
1255         p->input_format == VAL_GRS1)
1256     {
1257         oident oe;
1258         Odr_oid *oid;
1259         int oidtmp[OID_SIZE];
1260         
1261         oe.proto = PROTO_Z3950;
1262         oe.oclass = CLASS_SCHEMA;
1263         oe.value = node->u.root.absyn->reference;
1264         
1265         if ((oid = oid_ent_to_oid (&oe, oidtmp)))
1266         {
1267             char tmp[128];
1268             data1_handle dh = p->dh;
1269             char *p = tmp;
1270             int *ii;
1271             
1272             for (ii = oid; *ii >= 0; ii++)
1273             {
1274                 if (p != tmp)
1275                         *(p++) = '.';
1276                 sprintf(p, "%d", *ii);
1277                 p += strlen(p);
1278             }
1279             if ((dnew = data1_mk_tag_data_wd(dh, top, 
1280                                              "schemaIdentifier", mem)))
1281             {
1282                 dnew->u.data.what = DATA1I_oid;
1283                 dnew->u.data.data = (char *) nmem_malloc(mem, p - tmp);
1284                 memcpy(dnew->u.data.data, tmp, p - tmp);
1285                 dnew->u.data.len = p - tmp;
1286             }
1287         }
1288     }
1289
1290     yaz_log(YLOG_DEBUG, "grs_retrieve: element spec");
1291     if (p->comp && (res = process_comp(p->dh, node, p->comp, &p->addinfo,
1292                                        p->odr)) > 0)
1293     {
1294         p->diagnostic = res;
1295         nmem_destroy(mem);
1296         return 0;
1297     }
1298     else if (p->comp && !res)
1299         selected = 1;
1300
1301 #if 0
1302     data1_pr_tree (p->dh, node, stdout);
1303 #endif
1304     yaz_log(YLOG_DEBUG, "grs_retrieve: transfer syntax mapping");
1305     switch (p->output_format = (p->input_format != VAL_NONE ?
1306                                 p->input_format : VAL_SUTRS))
1307     {
1308     case VAL_TEXT_XML:
1309 #if 0
1310         data1_pr_tree (p->dh, node, stdout);
1311 #endif
1312         /* default output encoding for XML is UTF-8 */
1313         data1_iconv (p->dh, mem, node,
1314                      p->encoding ? p->encoding : "UTF-8",
1315                      data1_get_encoding(p->dh, node));
1316
1317         if (!(p->rec_buf = data1_nodetoidsgml(p->dh, node, selected,
1318                                               &p->rec_len)))
1319             p->diagnostic = 238;
1320         else
1321         {
1322             char *new_buf = (char*) odr_malloc (p->odr, p->rec_len);
1323             memcpy (new_buf, p->rec_buf, p->rec_len);
1324             p->rec_buf = new_buf;
1325         }
1326         break;
1327     case VAL_GRS1:
1328         data1_iconv (p->dh, mem, node, "UTF-8", data1_get_encoding(p->dh, node));
1329         dummy = 0;
1330         if (!(p->rec_buf = data1_nodetogr(p->dh, node, selected,
1331                                           p->odr, &dummy)))
1332             p->diagnostic = 238; /* not available in requested syntax */
1333         else
1334             p->rec_len = -1;
1335         break;
1336     case VAL_EXPLAIN:
1337         /* ensure our data1 tree is UTF-8 */
1338         data1_iconv (p->dh, mem, node, "UTF-8", data1_get_encoding(p->dh, node));
1339         
1340         if (!(p->rec_buf = data1_nodetoexplain(p->dh, node, selected,
1341                                                p->odr)))
1342             p->diagnostic = 238;
1343         else
1344             p->rec_len = -1;
1345         break;
1346     case VAL_SUMMARY:
1347         /* ensure our data1 tree is UTF-8 */
1348         data1_iconv (p->dh, mem, node, "UTF-8", data1_get_encoding(p->dh, node));
1349         if (!(p->rec_buf = data1_nodetosummary(p->dh, node, selected,
1350                                                p->odr)))
1351             p->diagnostic = 238;
1352         else
1353             p->rec_len = -1;
1354         break;
1355     case VAL_SUTRS:
1356         if (p->encoding)
1357             data1_iconv (p->dh, mem, node, p->encoding,
1358                          data1_get_encoding(p->dh, node));
1359         if (!(p->rec_buf = data1_nodetobuf(p->dh, node, selected,
1360                                            &p->rec_len)))
1361             p->diagnostic = 238;
1362         else
1363         {
1364             char *new_buf = (char*) odr_malloc (p->odr, p->rec_len);
1365             memcpy (new_buf, p->rec_buf, p->rec_len);
1366             p->rec_buf = new_buf;
1367         }
1368         break;
1369     case VAL_SOIF:
1370         if (p->encoding)
1371             data1_iconv (p->dh, mem, node, p->encoding,
1372                          data1_get_encoding(p->dh, node));
1373         if (!(p->rec_buf = data1_nodetosoif(p->dh, node, selected,
1374                                             &p->rec_len)))
1375             p->diagnostic = 238;
1376         else
1377         {
1378             char *new_buf = (char*) odr_malloc (p->odr, p->rec_len);
1379             memcpy (new_buf, p->rec_buf, p->rec_len);
1380             p->rec_buf = new_buf;
1381         }
1382         break;
1383     default:
1384         if (!node->u.root.absyn)
1385         {
1386             p->diagnostic = 238;
1387             break;
1388         }
1389         for (marctab = node->u.root.absyn->marc; marctab;
1390              marctab = marctab->next)
1391             if (marctab->reference == p->input_format)
1392                 break;
1393         if (!marctab)
1394         {
1395             p->diagnostic = 238;
1396             break;
1397         }
1398         if (p->encoding)
1399             data1_iconv (p->dh, mem, node, p->encoding,
1400                          data1_get_encoding(p->dh, node));
1401         if (!(p->rec_buf = data1_nodetomarc(p->dh, marctab, node,
1402                                         selected, &p->rec_len)))
1403             p->diagnostic = 238;
1404         else
1405         {
1406             char *new_buf = (char*) odr_malloc (p->odr, p->rec_len);
1407             memcpy (new_buf, p->rec_buf, p->rec_len);
1408                 p->rec_buf = new_buf;
1409         }
1410     }
1411     nmem_destroy(mem);
1412     return 0;
1413 }
1414
1415 /*
1416  * Local variables:
1417  * c-basic-offset: 4
1418  * indent-tabs-mode: nil
1419  * End:
1420  * vim: shiftwidth=4 tabstop=8 expandtab
1421  */
1422