Using the new ylog.h everywhere, and fixing what that breaks!
[idzebra-moved-to-github.git] / recctrl / recgrs.c
1 /* $Id: recgrs.c,v 1.93 2004-11-19 10:27:12 heikki 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 {
905     data1_esetname *eset;
906     Z_Espec1 *espec = 0;
907     Z_ElementSpec *p;
908
909     switch (c->which)
910     {
911     case Z_RecordComp_simple:
912         if (c->u.simple->which != Z_ElementSetNames_generic)
913             return 26; /* only generic form supported. Fix this later */
914         if (!(eset = data1_getesetbyname(dh, n->u.root.absyn,
915                                          c->u.simple->u.generic)))
916         {
917             yaz_log(YLOG_LOG, "Unknown esetname '%s'", c->u.simple->u.generic);
918             return 25; /* invalid esetname */
919         }
920         yaz_log(YLOG_DEBUG, "Esetname '%s' in simple compspec",
921              c->u.simple->u.generic);
922         espec = eset->spec;
923         break;
924     case Z_RecordComp_complex:
925         if (c->u.complex->generic)
926         {
927             /* insert check for schema */
928             if ((p = c->u.complex->generic->elementSpec))
929             {
930                 switch (p->which)
931                 {
932                 case Z_ElementSpec_elementSetName:
933                     if (!(eset =
934                           data1_getesetbyname(dh, n->u.root.absyn,
935                                               p->u.elementSetName)))
936                     {
937                         yaz_log(YLOG_LOG, "Unknown esetname '%s'",
938                              p->u.elementSetName);
939                         return 25; /* invalid esetname */
940                     }
941                     yaz_log(YLOG_DEBUG, "Esetname '%s' in complex compspec",
942                          p->u.elementSetName);
943                     espec = eset->spec;
944                     break;
945                 case Z_ElementSpec_externalSpec:
946                     if (p->u.externalSpec->which == Z_External_espec1)
947                     {
948                         yaz_log(YLOG_DEBUG, "Got Espec-1");
949                         espec = p->u.externalSpec-> u.espec1;
950                     }
951                     else
952                     {
953                         yaz_log(YLOG_LOG, "Unknown external espec.");
954                         return 25; /* bad. what is proper diagnostic? */
955                     }
956                     break;
957                 }
958             }
959         }
960         else
961             return 26; /* fix */
962     }
963     if (espec)
964     {
965         yaz_log(YLOG_DEBUG, "Element: Espec-1 match");
966         return data1_doespec1(dh, n, espec);
967     }
968     else
969     {
970         yaz_log(YLOG_DEBUG, "Element: all match");
971         return -1;
972     }
973 }
974
975 /* Add Zebra info in separate namespace ...
976         <root 
977          ...
978          <metadata xmlns="http://www.indexdata.dk/zebra/">
979           <size>359</size>
980           <localnumber>447</localnumber>
981           <filename>records/genera.xml</filename>
982          </metadata>
983         </root>
984 */
985
986 static void zebra_xml_metadata (struct recRetrieveCtrl *p, data1_node *top,
987                                 NMEM mem)
988 {
989     const char *idzebra_ns[3];
990     const char *i2 = "\n  ";
991     const char *i4 = "\n    ";
992     data1_node *n;
993
994     idzebra_ns[0] = "xmlns";
995     idzebra_ns[1] = "http://www.indexdata.dk/zebra/";
996     idzebra_ns[2] = 0;
997
998     data1_mk_text (p->dh, mem, i2, top);
999
1000     n = data1_mk_tag (p->dh, mem, "idzebra", idzebra_ns, top);
1001
1002     data1_mk_text (p->dh, mem, "\n", top);
1003
1004     data1_mk_text (p->dh, mem, i4, n);
1005     
1006     data1_mk_tag_data_int (p->dh, n, "size", p->recordSize, mem);
1007
1008     if (p->score != -1)
1009     {
1010         data1_mk_text (p->dh, mem, i4, n);
1011         data1_mk_tag_data_int (p->dh, n, "score", p->score, mem);
1012     }
1013     data1_mk_text (p->dh, mem, i4, n);
1014     data1_mk_tag_data_zint (p->dh, n, "localnumber", p->localno, mem);
1015     if (p->fname)
1016     {
1017         data1_mk_text (p->dh, mem, i4, n);
1018         data1_mk_tag_data_text(p->dh, n, "filename", p->fname, mem);
1019     }
1020     data1_mk_text (p->dh, mem, i2, n);
1021 }
1022
1023 int zebra_grs_retrieve(void *clientData, struct recRetrieveCtrl *p,
1024                        data1_node *(*grs_read)(struct grs_read_info *))
1025 {
1026     data1_node *node = 0, *onode = 0, *top;
1027     data1_node *dnew;
1028     data1_maptab *map;
1029     int res, selected = 0;
1030     NMEM mem;
1031     struct grs_read_info gri;
1032     const char *tagname;
1033
1034     int requested_schema = VAL_NONE;
1035     data1_marctab *marctab;
1036     int dummy;
1037     
1038     mem = nmem_create();
1039     gri.readf = p->readf;
1040     gri.seekf = p->seekf;
1041     gri.tellf = p->tellf;
1042     gri.endf = NULL;
1043     gri.fh = p->fh;
1044     gri.offset = 0;
1045     gri.mem = mem;
1046     gri.dh = p->dh;
1047     gri.clientData = clientData;
1048
1049     yaz_log(YLOG_DEBUG, "grs_retrieve");
1050     node = (*grs_read)(&gri);
1051     if (!node)
1052     {
1053         p->diagnostic = 14;
1054         nmem_destroy (mem);
1055         return 0;
1056     }
1057     data1_concat_text(p->dh, mem, node);
1058
1059     /* ensure our data1 tree is UTF-8 */
1060     data1_iconv (p->dh, mem, node, "UTF-8", data1_get_encoding(p->dh, node));
1061
1062 #if 0
1063     data1_pr_tree (p->dh, node, stdout);
1064 #endif
1065     top = data1_get_root_tag (p->dh, node);
1066
1067     yaz_log(YLOG_DEBUG, "grs_retrieve: size");
1068     tagname = data1_systag_lookup(node->u.root.absyn, "size", "size");
1069     if (tagname &&
1070         (dnew = data1_mk_tag_data_wd(p->dh, top, tagname, mem)))
1071     {
1072         dnew->u.data.what = DATA1I_text;
1073         dnew->u.data.data = dnew->lbuf;
1074         sprintf(dnew->u.data.data, "%d", p->recordSize);
1075         dnew->u.data.len = strlen(dnew->u.data.data);
1076     }
1077     
1078     tagname = data1_systag_lookup(node->u.root.absyn, "rank", "rank");
1079     if (tagname && p->score >= 0 &&
1080         (dnew = data1_mk_tag_data_wd(p->dh, top, tagname, mem)))
1081     {
1082         yaz_log(YLOG_DEBUG, "grs_retrieve: %s", tagname);
1083         dnew->u.data.what = DATA1I_num;
1084         dnew->u.data.data = dnew->lbuf;
1085         sprintf(dnew->u.data.data, "%d", p->score);
1086         dnew->u.data.len = strlen(dnew->u.data.data);
1087     }
1088
1089     tagname = data1_systag_lookup(node->u.root.absyn, "sysno",
1090                                   "localControlNumber");
1091     if (tagname && p->localno > 0 &&
1092         (dnew = data1_mk_tag_data_wd(p->dh, top, tagname, mem)))
1093     {
1094         yaz_log(YLOG_DEBUG, "grs_retrieve: %s", tagname);
1095         dnew->u.data.what = DATA1I_text;
1096         dnew->u.data.data = dnew->lbuf;
1097         
1098         sprintf(dnew->u.data.data, ZINT_FORMAT, p->localno);
1099         dnew->u.data.len = strlen(dnew->u.data.data);
1100     }
1101
1102     if (p->input_format == VAL_TEXT_XML)
1103        zebra_xml_metadata (p, top, mem);
1104
1105 #if 0
1106     data1_pr_tree (p->dh, node, stdout);
1107 #endif
1108 #if YAZ_VERSIONL >= 0x010903L
1109     if (p->comp && p->comp->which == Z_RecordComp_complex &&
1110         p->comp->u.complex->generic &&
1111         p->comp->u.complex->generic->which == Z_Schema_oid &&
1112         p->comp->u.complex->generic->schema.oid)
1113     {
1114         oident *oe = oid_getentbyoid (p->comp->u.complex->generic->schema.oid);
1115         if (oe)
1116             requested_schema = oe->value;
1117     }
1118 #else
1119     if (p->comp && p->comp->which == Z_RecordComp_complex &&
1120         p->comp->u.complex->generic && p->comp->u.complex->generic->schema)
1121     {
1122         oident *oe = oid_getentbyoid (p->comp->u.complex->generic->schema);
1123         if (oe)
1124             requested_schema = oe->value;
1125     }
1126 #endif
1127
1128     /* If schema has been specified, map if possible, then check that
1129      * we got the right one 
1130      */
1131     if (requested_schema != VAL_NONE)
1132     {
1133         yaz_log(YLOG_DEBUG, "grs_retrieve: schema mapping");
1134         for (map = node->u.root.absyn->maptabs; map; map = map->next)
1135         {
1136             if (map->target_absyn_ref == requested_schema)
1137             {
1138                 onode = node;
1139                 if (!(node = data1_map_record(p->dh, onode, map, mem)))
1140                 {
1141                     p->diagnostic = 14;
1142                     nmem_destroy (mem);
1143                     return 0;
1144                 }
1145                 break;
1146             }
1147         }
1148         if (node->u.root.absyn &&
1149             requested_schema != node->u.root.absyn->reference)
1150         {
1151             p->diagnostic = 238;
1152             nmem_destroy (mem);
1153             return 0;
1154         }
1155     }
1156     /*
1157      * Does the requested format match a known syntax-mapping? (this reflects
1158      * the overlap of schema and formatting which is inherent in the MARC
1159      * family)
1160      */
1161     yaz_log(YLOG_DEBUG, "grs_retrieve: syntax mapping");
1162     if (node->u.root.absyn)
1163         for (map = node->u.root.absyn->maptabs; map; map = map->next)
1164         {
1165             if (map->target_absyn_ref == p->input_format)
1166             {
1167                 onode = node;
1168                 if (!(node = data1_map_record(p->dh, onode, map, mem)))
1169                 {
1170                     p->diagnostic = 14;
1171                     nmem_destroy (mem);
1172                     return 0;
1173                 }
1174                 break;
1175             }
1176         }
1177     yaz_log(YLOG_DEBUG, "grs_retrieve: schemaIdentifier");
1178     if (node->u.root.absyn &&
1179         node->u.root.absyn->reference != VAL_NONE &&
1180         p->input_format == VAL_GRS1)
1181     {
1182         oident oe;
1183         Odr_oid *oid;
1184         int oidtmp[OID_SIZE];
1185         
1186         oe.proto = PROTO_Z3950;
1187         oe.oclass = CLASS_SCHEMA;
1188         oe.value = node->u.root.absyn->reference;
1189         
1190         if ((oid = oid_ent_to_oid (&oe, oidtmp)))
1191         {
1192             char tmp[128];
1193             data1_handle dh = p->dh;
1194             char *p = tmp;
1195             int *ii;
1196             
1197             for (ii = oid; *ii >= 0; ii++)
1198             {
1199                 if (p != tmp)
1200                         *(p++) = '.';
1201                 sprintf(p, "%d", *ii);
1202                 p += strlen(p);
1203             }
1204             if ((dnew = data1_mk_tag_data_wd(dh, top, 
1205                                              "schemaIdentifier", mem)))
1206             {
1207                 dnew->u.data.what = DATA1I_oid;
1208                 dnew->u.data.data = (char *) nmem_malloc(mem, p - tmp);
1209                 memcpy(dnew->u.data.data, tmp, p - tmp);
1210                 dnew->u.data.len = p - tmp;
1211             }
1212         }
1213     }
1214
1215     yaz_log(YLOG_DEBUG, "grs_retrieve: element spec");
1216     if (p->comp && (res = process_comp(p->dh, node, p->comp)) > 0)
1217     {
1218         p->diagnostic = res;
1219         if (onode)
1220             data1_free_tree(p->dh, onode);
1221         data1_free_tree(p->dh, node);
1222         nmem_destroy(mem);
1223         return 0;
1224     }
1225     else if (p->comp && !res)
1226         selected = 1;
1227
1228 #if 0
1229     data1_pr_tree (p->dh, node, stdout);
1230 #endif
1231     yaz_log(YLOG_DEBUG, "grs_retrieve: transfer syntax mapping");
1232     switch (p->output_format = (p->input_format != VAL_NONE ?
1233                                 p->input_format : VAL_SUTRS))
1234     {
1235     case VAL_TEXT_XML:
1236
1237 #if 0
1238         data1_pr_tree (p->dh, node, stdout);
1239 #endif
1240
1241         if (p->encoding)
1242             data1_iconv (p->dh, mem, node, p->encoding, "UTF-8");
1243
1244         if (!(p->rec_buf = data1_nodetoidsgml(p->dh, node, selected,
1245                                               &p->rec_len)))
1246             p->diagnostic = 238;
1247         else
1248         {
1249             char *new_buf = (char*) odr_malloc (p->odr, p->rec_len);
1250             memcpy (new_buf, p->rec_buf, p->rec_len);
1251             p->rec_buf = new_buf;
1252         }
1253         break;
1254     case VAL_GRS1:
1255         dummy = 0;
1256         if (!(p->rec_buf = data1_nodetogr(p->dh, node, selected,
1257                                           p->odr, &dummy)))
1258             p->diagnostic = 238; /* not available in requested syntax */
1259         else
1260             p->rec_len = (size_t) (-1);
1261         break;
1262     case VAL_EXPLAIN:
1263         if (!(p->rec_buf = data1_nodetoexplain(p->dh, node, selected,
1264                                                p->odr)))
1265             p->diagnostic = 238;
1266         else
1267             p->rec_len = (size_t) (-1);
1268         break;
1269     case VAL_SUMMARY:
1270         if (!(p->rec_buf = data1_nodetosummary(p->dh, node, selected,
1271                                                p->odr)))
1272             p->diagnostic = 238;
1273         else
1274             p->rec_len = (size_t) (-1);
1275         break;
1276     case VAL_SUTRS:
1277         if (p->encoding)
1278             data1_iconv (p->dh, mem, node, p->encoding, "UTF-8");
1279         if (!(p->rec_buf = data1_nodetobuf(p->dh, node, selected,
1280                                            &p->rec_len)))
1281             p->diagnostic = 238;
1282         else
1283         {
1284             char *new_buf = (char*) odr_malloc (p->odr, p->rec_len);
1285             memcpy (new_buf, p->rec_buf, p->rec_len);
1286             p->rec_buf = new_buf;
1287         }
1288         break;
1289     case VAL_SOIF:
1290         if (!(p->rec_buf = data1_nodetosoif(p->dh, node, selected,
1291                                             &p->rec_len)))
1292             p->diagnostic = 238;
1293         else
1294         {
1295             char *new_buf = (char*) odr_malloc (p->odr, p->rec_len);
1296             memcpy (new_buf, p->rec_buf, p->rec_len);
1297             p->rec_buf = new_buf;
1298         }
1299         break;
1300     default:
1301         if (!node->u.root.absyn)
1302         {
1303             p->diagnostic = 238;
1304             break;
1305         }
1306         for (marctab = node->u.root.absyn->marc; marctab;
1307              marctab = marctab->next)
1308             if (marctab->reference == p->input_format)
1309                 break;
1310         if (!marctab)
1311         {
1312             p->diagnostic = 238;
1313             break;
1314         }
1315         if (p->encoding)
1316             data1_iconv (p->dh, mem, node, p->encoding, "UTF-8");
1317         if (!(p->rec_buf = data1_nodetomarc(p->dh, marctab, node,
1318                                         selected, &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     }
1327     if (node)
1328         data1_free_tree(p->dh, node);
1329     if (onode)
1330         data1_free_tree(p->dh, onode);
1331     nmem_destroy(mem);
1332     return 0;
1333 }
1334