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