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