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