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