Fixed bug #685: Optimize xelm/melm matching. Indexing the Koha collection
[idzebra-moved-to-github.git] / index / recgrs.c
1 /* $Id: recgrs.c,v 1.6 2006-09-28 18:38:47 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()
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->next)
422             {
423                 if (!strcmp(xpe1->regexp, xpe->regexp))
424                     xpe1->match_state = ok;
425             }
426 #endif
427         }
428         assert (ok == 0 || ok == 1);
429         if (ok) {
430 #ifdef ENHANCED_XELM 
431             /* we have to check the perdicates up to the root node */
432             xp = xpe->xpath;
433             
434             /* find the first tag up in the node structure */
435             for (nn = n; nn && nn->which != DATA1N_tag; nn = nn->parent)
436                 ;
437             
438             /* go from inside out in the node structure, while going
439                backwards trough xpath location steps ... */
440             for (i = xpe->xpath_len - 1; i>0; i--)
441             {
442                 yaz_log(YLOG_DEBUG, "Checking step %d: %s on tag %s",
443                         i, xp[i].part, nn->u.tag.tag);
444                 
445                 if (!d1_check_xpath_predicate(nn, xp[i].predicate))
446                 {
447                     yaz_log(YLOG_DEBUG, "  Predicates didn't match");
448                     ok = 0;
449                     break;
450                 }
451                 
452                 if (nn->which == DATA1N_tag)
453                     nn = nn->parent;
454             }
455 #endif
456             if (ok)
457                 break;
458         }
459     } 
460     
461     xfree(pexpr);
462     
463     if (xpe) {
464         yaz_log(YLOG_DEBUG, "Got it");
465         return xpe->termlists;
466     } else {
467         return NULL;
468     }
469 }
470
471 /* use
472      1   start element (tag)
473      2   end element
474      3   start attr (and attr-exact)
475      4   end attr
476
477   1016   cdata
478   1015   attr data
479
480   *ostrich*
481
482   Now, if there is a matching xelm described in abs, for the
483   indexed element or the attribute,  then the data is handled according 
484   to those definitions...
485
486   modified by pop, 2002-12-13
487 */
488
489 /* add xpath index for an attribute */
490 static void index_xpath_attr (char *tag_path, char *name, char *value,
491                               char *structure, struct recExtractCtrl *p,
492                               RecWord *wrd)
493 {
494     wrd->index_name = ZEBRA_XPATH_ELM_BEGIN;
495     wrd->index_type = '0';
496     wrd->term_buf = tag_path;
497     wrd->term_len = strlen(tag_path);
498     (*p->tokenAdd)(wrd);
499     
500     if (value) {
501         wrd->index_name = ZEBRA_XPATH_ATTR_CDATA;
502         wrd->index_type = 'w';
503         wrd->term_buf = value;
504         wrd->term_len = strlen(value);
505         (*p->tokenAdd)(wrd);
506     }
507     wrd->index_name = ZEBRA_XPATH_ELM_END;
508     wrd->index_type = '0';
509     wrd->term_buf = tag_path;
510     wrd->term_len = strlen(tag_path);
511     (*p->tokenAdd)(wrd);
512 }
513
514
515 static void mk_tag_path_full(char *tag_path_full, size_t max, data1_node *n)
516 {
517     size_t flen = 0;
518     data1_node *nn;
519
520     /* we have to fetch the whole path to the data tag */
521     for (nn = n; nn; nn = nn->parent)
522     {
523         if (nn->which == DATA1N_tag)
524         {
525             size_t tlen = strlen(nn->u.tag.tag);
526             if (tlen + flen > (max - 2))
527                 break;
528             memcpy (tag_path_full + flen, nn->u.tag.tag, tlen);
529             flen += tlen;
530             tag_path_full[flen++] = '/';
531         }
532         else
533             if (nn->which == DATA1N_root)
534                 break;
535     }
536     tag_path_full[flen] = 0;
537 }
538         
539
540 static void index_xpath(struct source_parser *sp, data1_node *n,
541                         struct recExtractCtrl *p,
542                         int level, RecWord *wrd,
543                         char *xpath_index,
544                         int xpath_is_start
545     )
546 {
547     int i;
548     char tag_path_full[1024];
549     int termlist_only = 1;
550     data1_termlist *tl;
551     int xpdone = 0;
552     if ((!n->root->u.root.absyn) ||
553         (n->root->u.root.absyn->xpath_indexing == DATA1_XPATH_INDEXING_ENABLE)) {
554         termlist_only = 0;
555     }
556
557     switch (n->which)
558     {
559     case DATA1N_data:
560         wrd->term_buf = n->u.data.data;
561         wrd->term_len = n->u.data.len;
562         xpdone = 0;
563
564         mk_tag_path_full(tag_path_full, sizeof(tag_path_full), n);
565         
566         /* If we have a matching termlist... */
567         if (n->root->u.root.absyn && 
568             (tl = xpath_termlist_by_tagpath(tag_path_full, n)))
569         {
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                 if (!tl->index_name)
579                 {
580                     /* this is the ! case, so structure is for the xpath index */
581                     wrd_tl.index_name = xpath_index;
582                     if (p->flagShowRecords)
583                     {
584                         int i;
585                         printf("%*sXPath index", (level + 1) * 4, "");
586                         printf (" XData:\"");
587                         for (i = 0; i<wrd_tl.term_len && i < 40; i++)
588                             fputc (wrd_tl.term_buf[i], stdout);
589                         fputc ('"', stdout);
590                         if (wrd_tl.term_len > 40)
591                             printf (" ...");
592                         fputc ('\n', stdout);
593                     }
594                     else
595                         (*p->tokenAdd)(&wrd_tl);
596                     xpdone = 1;
597                 } else {
598                     /* this is just the old fashioned attribute based index */
599                     wrd_tl.index_name = tl->index_name;
600                     if (p->flagShowRecords)
601                     {
602                         int i;
603                         printf("%*sIdx: [%s]", (level + 1) * 4, "",
604                                tl->structure);
605                         printf("%s %s", tl->index_name, tl->source);
606                         printf (" XData:\"");
607                         for (i = 0; i<wrd_tl.term_len && i < 40; i++)
608                             fputc (wrd_tl.term_buf[i], stdout);
609                         fputc ('"', stdout);
610                         if (wrd_tl.term_len > 40)
611                             printf (" ...");
612                         fputc ('\n', stdout);
613                     }
614                     else
615                         (*p->tokenAdd)(&wrd_tl);
616                 }
617             }
618         }
619         /* xpath indexing is done, if there was no termlist given, 
620            or no ! in the termlist, and default indexing is enabled... */
621         if (!p->flagShowRecords && !xpdone && !termlist_only)
622         {
623             wrd->index_name = xpath_index;
624             wrd->index_type = 'w';
625             (*p->tokenAdd)(wrd);
626         }
627         break;
628     case DATA1N_tag:
629         mk_tag_path_full(tag_path_full, sizeof(tag_path_full), n);
630
631         wrd->index_type = '0';
632         wrd->term_buf = tag_path_full;
633         wrd->term_len = strlen(tag_path_full);
634         wrd->index_name = xpath_index;
635         if (p->flagShowRecords)
636         {
637             printf("%*s tag=", (level + 1) * 4, "");
638             for (i = 0; i<wrd->term_len && i < 40; i++)
639                 fputc (wrd->term_buf[i], stdout);
640             if (i == 40)
641                 printf (" ..");
642             printf("\n");
643         }
644         else
645         {
646             data1_xattr *xp;
647             data1_termlist *tl;
648             int do_xpindex;
649             
650             /* Add tag start/end xpath index, only when there is a ! in
651                the apropriate xelm directive, or default xpath indexing
652                is enabled 
653             */
654             if (!(do_xpindex = 1 - termlist_only))
655             {
656                 if ((tl = xpath_termlist_by_tagpath(tag_path_full, n))) 
657                 {
658                     for (; tl; tl = tl->next) 
659                     {
660                         if (!tl->index_name)
661                             do_xpindex = 1;
662                     }
663                 }
664             }
665             if (do_xpindex) {
666                 (*p->tokenAdd)(wrd);   /* index element pag (AKA tag path) */
667             }
668             
669             if (xpath_is_start == 1) /* only for the starting tag... */
670             {
671 #define MAX_ATTR_COUNT 50
672                 data1_termlist *tll[MAX_ATTR_COUNT];
673                 
674                 int i = 0;
675                 
676                 /* get termlists for attributes, and find out, if we have to do xpath indexing */
677                 for (xp = n->u.tag.attributes; xp; xp = xp->next) {
678                     i++;
679                 }
680                 
681                 i = 0;
682                 for (xp = n->u.tag.attributes; xp; xp = xp->next) {
683                     char comb[512];
684                     int do_xpindex = 1 - termlist_only;
685                     data1_termlist *tl;
686                     char attr_tag_path_full[1024]; 
687                     
688                     /* this could be cached as well */
689                     sprintf (attr_tag_path_full, "@%s/%s",
690                              xp->name, tag_path_full);
691                     
692                     tll[i] = xpath_termlist_by_tagpath(attr_tag_path_full,n);
693                     
694                     /* if there is a ! in the xelm termlist, or default indexing is on, 
695                        proceed with xpath idx */
696                     if ((tl = tll[i]))
697                     {
698                         for (; tl; tl = tl->next)
699                         {
700                             if (!tl->index_name)
701                                 do_xpindex = 1;
702                         }
703                     }
704                     
705                     if (do_xpindex) {
706                         
707                         /* attribute  (no value) */
708                         wrd->index_type = '0';
709                         wrd->index_name = ZEBRA_XPATH_ATTR_NAME;
710                         wrd->term_buf = xp->name;
711                         wrd->term_len = strlen(xp->name);
712                         
713                         wrd->seqno--;
714                         (*p->tokenAdd)(wrd);
715                         
716                         if (xp->value &&
717                             strlen(xp->name) + strlen(xp->value) < sizeof(comb)-2) {
718                             
719                             /* attribute value exact */
720                             strcpy (comb, xp->name);
721                             strcat (comb, "=");
722                             strcat (comb, xp->value);
723
724                             wrd->index_name = ZEBRA_XPATH_ATTR_NAME;
725                             wrd->index_type = '0';
726                             wrd->term_buf = comb;
727                             wrd->term_len = strlen(comb);
728                             wrd->seqno--;
729                             
730                             (*p->tokenAdd)(wrd);
731                         }
732                     }                
733                     i++;
734                 }
735                 
736                 i = 0;
737                 for (xp = n->u.tag.attributes; xp; xp = xp->next) {
738                     data1_termlist *tl;
739                     char attr_tag_path_full[1024];
740                     int xpdone = 0;
741                     
742                     sprintf (attr_tag_path_full, "@%s/%s",
743                              xp->name, tag_path_full);
744                     
745                     if ((tl = tll[i]))
746                     {
747                         /* If there is a termlist given (=xelm directive) */
748                         for (; tl; tl = tl->next)
749                         {
750                             if (!tl->index_name)
751                             {
752                                 /* add xpath index for the attribute */
753                                 index_xpath_attr (attr_tag_path_full, xp->name,
754                                                   xp->value, tl->structure,
755                                                   p, wrd);
756                                 xpdone = 1;
757                             } else {
758                                 /* index attribute value (only path/@attr) */
759                                 if (xp->value) 
760                                 {
761                                     wrd->index_name = tl->index_name;
762                                     wrd->index_type = *tl->structure;
763                                     wrd->term_buf = xp->value;
764                                     wrd->term_len = strlen(xp->value);
765                                     (*p->tokenAdd)(wrd);
766                                 }
767                             }
768                         }
769                     }
770                     /* if there was no termlist for the given path, 
771                        or the termlist didn't have a ! element, index 
772                        the attribute as "w" */
773                     if ((!xpdone) && (!termlist_only))
774                     {
775                         index_xpath_attr (attr_tag_path_full, xp->name,
776                                           xp->value,  "w", p, wrd);
777                     }
778                     i++;
779                 }
780             }
781         }
782     }
783 }
784
785 static void index_termlist (struct source_parser *sp, data1_node *par,
786                             data1_node *n,
787                             struct recExtractCtrl *p, int level, RecWord *wrd)
788 {
789     data1_termlist *tlist = 0;
790     data1_datatype dtype = DATA1K_string;
791
792     /*
793      * cycle up towards the root until we find a tag with an att..
794      * this has the effect of indexing locally defined tags with
795      * the attribute of their ancestor in the record.
796      */
797     
798     while (!par->u.tag.element)
799         if (!par->parent || !(par=get_parent_tag(p->dh, par->parent)))
800             break;
801     if (!par || !(tlist = par->u.tag.element->termlists))
802         return;
803     if (par->u.tag.element->tag)
804         dtype = par->u.tag.element->tag->kind;
805
806     for (; tlist; tlist = tlist->next)
807     {
808         /* consider source */
809         wrd->term_buf = 0;
810         assert(tlist->source);
811         sp_parse(sp, n, wrd, tlist->source);
812
813         if (wrd->term_buf && wrd->term_len)
814         {
815             if (p->flagShowRecords)
816             {
817                 int i;
818                 printf("%*sIdx: [%s]", (level + 1) * 4, "",
819                        tlist->structure);
820                 printf("%s %s", tlist->index_name, tlist->source);
821                 printf (" XData:\"");
822                 for (i = 0; i<wrd->term_len && i < 40; i++)
823                     fputc (wrd->term_buf[i], stdout);
824                 fputc ('"', stdout);
825                 if (wrd->term_len > 40)
826                     printf (" ...");
827                 fputc ('\n', stdout);
828             }
829             else
830             {
831                 wrd->index_type = *tlist->structure;
832                 wrd->index_name = tlist->index_name;
833                 (*p->tokenAdd)(wrd);
834             }
835         }
836     }
837 }
838
839 static int dumpkeys_r(struct source_parser *sp,
840                       data1_node *n, struct recExtractCtrl *p, int level,
841                       RecWord *wrd)
842 {
843     for (; n; n = n->next)
844     {
845         if (p->flagShowRecords) /* display element description to user */
846         {
847             if (n->which == DATA1N_root)
848             {
849                 printf("%*s", level * 4, "");
850                 printf("Record type: '%s'\n", n->u.root.type);
851             }
852             else if (n->which == DATA1N_tag)
853             {
854                 data1_element *e;
855
856                 printf("%*s", level * 4, "");
857                 if (!(e = n->u.tag.element))
858                     printf("Local tag: '%s'\n", n->u.tag.tag);
859                 else
860                 {
861                     printf("Elm: '%s' ", e->name);
862                     if (e->tag)
863                     {
864                         data1_tag *t = e->tag;
865
866                         printf("TagNam: '%s' ", t->names->name);
867                         printf("(");
868                         if (t->tagset)
869                             printf("%s[%d],", t->tagset->name, t->tagset->type);
870                         else
871                             printf("?,");
872                         if (t->which == DATA1T_numeric)
873                             printf("%d)", t->value.numeric);
874                         else
875                             printf("'%s')", t->value.string);
876                     }
877                     printf("\n");
878                 }
879             }
880         }
881
882         if (n->which == DATA1N_tag)
883         {
884             index_termlist(sp, n, n, p, level, wrd);
885             /* index start tag */
886             if (n->root->u.root.absyn)
887                 index_xpath(sp, n, p, level, wrd, ZEBRA_XPATH_ELM_BEGIN, 
888                             1 /* is start */);
889         }
890
891         if (n->child)
892             if (dumpkeys_r(sp, n->child, p, level + 1, wrd) < 0)
893                 return -1;
894
895
896         if (n->which == DATA1N_data)
897         {
898             data1_node *par = get_parent_tag(p->dh, n);
899
900             if (p->flagShowRecords)
901             {
902                 printf("%*s", level * 4, "");
903                 printf("Data: ");
904                 if (n->u.data.len > 256)
905                     printf("'%.170s ... %.70s'\n", n->u.data.data,
906                            n->u.data.data + n->u.data.len-70);
907                 else if (n->u.data.len > 0)
908                     printf("'%.*s'\n", n->u.data.len, n->u.data.data);
909                 else
910                     printf("NULL\n");
911             }
912
913             if (par)
914                 index_termlist(sp, par, n, p, level, wrd);
915
916             index_xpath(sp, n, p, level, wrd, ZEBRA_XPATH_CDATA, 
917                         0 /* is start */);
918         }
919
920         if (n->which == DATA1N_tag)
921         {
922             /* index end tag */
923             index_xpath(sp, n, p, level, wrd, ZEBRA_XPATH_ELM_END, 
924                         0 /* is start */);
925         }
926
927         if (p->flagShowRecords && n->which == DATA1N_root)
928         {
929             printf("%*s-------------\n\n", level * 4, "");
930         }
931     }
932     return 0;
933 }
934
935 static int dumpkeys(data1_node *n, struct recExtractCtrl *p, RecWord *wrd)
936 {
937     struct source_parser *sp = source_parser_create();
938     int r = dumpkeys_r(sp, n, p, 0, wrd);
939     source_parser_destroy(sp);
940     return r;
941 }
942
943 int grs_extract_tree(struct recExtractCtrl *p, data1_node *n)
944 {
945     oident oe;
946     int oidtmp[OID_SIZE];
947     RecWord wrd;
948
949     oe.proto = PROTO_Z3950;
950     oe.oclass = CLASS_SCHEMA;
951     if (n->u.root.absyn)
952     {
953         oe.value = n->u.root.absyn->reference;
954         
955         if ((oid_ent_to_oid (&oe, oidtmp)))
956             (*p->schemaAdd)(p, oidtmp);
957     }
958     (*p->init)(p, &wrd);
959
960     /* data1_pr_tree(p->dh, n, stdout); */ 
961
962     return dumpkeys(n, p, &wrd);
963 }
964
965 static int grs_extract_sub(void *clientData, struct recExtractCtrl *p,
966                            NMEM mem,
967                            data1_node *(*grs_read)(struct grs_read_info *))
968 {
969     data1_node *n;
970     struct grs_read_info gri;
971     oident oe;
972     int oidtmp[OID_SIZE];
973     RecWord wrd;
974
975     gri.stream = p->stream;
976     gri.mem = mem;
977     gri.dh = p->dh;
978     gri.clientData = clientData;
979
980     n = (*grs_read)(&gri);
981     if (!n)
982         return RECCTRL_EXTRACT_EOF;
983     oe.proto = PROTO_Z3950;
984     oe.oclass = CLASS_SCHEMA;
985 #if 0
986     if (!n->u.root.absyn)
987         return RECCTRL_EXTRACT_ERROR;
988 #endif
989     if (n->u.root.absyn)
990     {
991         oe.value = n->u.root.absyn->reference;
992         if ((oid_ent_to_oid (&oe, oidtmp)))
993             (*p->schemaAdd)(p, oidtmp);
994     }
995     data1_concat_text(p->dh, mem, n);
996
997     /* ensure our data1 tree is UTF-8 */
998     data1_iconv (p->dh, mem, n, "UTF-8", data1_get_encoding(p->dh, n));
999
1000
1001     data1_remove_idzebra_subtree (p->dh, n);
1002
1003 #if 0
1004     data1_pr_tree (p->dh, n, stdout);
1005 #endif
1006
1007     (*p->init)(p, &wrd);
1008     if (dumpkeys(n, p, &wrd) < 0)
1009     {
1010         return RECCTRL_EXTRACT_ERROR_GENERIC;
1011     }
1012     return RECCTRL_EXTRACT_OK;
1013 }
1014
1015 int zebra_grs_extract(void *clientData, struct recExtractCtrl *p,
1016                       data1_node *(*grs_read)(struct grs_read_info *))
1017 {
1018     int ret;
1019     NMEM mem = nmem_create ();
1020     ret = grs_extract_sub(clientData, p, mem, grs_read);
1021     nmem_destroy(mem);
1022     return ret;
1023 }
1024
1025 /*
1026  * Return: -1: Nothing done. 0: Ok. >0: Bib-1 diagnostic.
1027  */
1028 static int process_comp(data1_handle dh, data1_node *n, Z_RecordComposition *c,
1029                         char **addinfo, ODR o)
1030 {
1031     data1_esetname *eset;
1032     Z_Espec1 *espec = 0;
1033     Z_ElementSpec *p;
1034
1035     switch (c->which)
1036     {
1037     case Z_RecordComp_simple:
1038         if (c->u.simple->which != Z_ElementSetNames_generic)
1039             return 26; /* only generic form supported. Fix this later */
1040         if (!(eset = data1_getesetbyname(dh, n->u.root.absyn,
1041                                          c->u.simple->u.generic)))
1042         {
1043             yaz_log(YLOG_LOG, "Unknown esetname '%s'", c->u.simple->u.generic);
1044             *addinfo = odr_strdup(o, c->u.simple->u.generic);
1045             return 25; /* invalid esetname */
1046         }
1047         yaz_log(YLOG_DEBUG, "Esetname '%s' in simple compspec",
1048              c->u.simple->u.generic);
1049         espec = eset->spec;
1050         break;
1051     case Z_RecordComp_complex:
1052         if (c->u.complex->generic)
1053         {
1054             /* insert check for schema */
1055             if ((p = c->u.complex->generic->elementSpec))
1056             {
1057                 switch (p->which)
1058                 {
1059                 case Z_ElementSpec_elementSetName:
1060                     if (!(eset =
1061                           data1_getesetbyname(dh, n->u.root.absyn,
1062                                               p->u.elementSetName)))
1063                     {
1064                         yaz_log(YLOG_DEBUG, "Unknown esetname '%s'",
1065                              p->u.elementSetName);
1066                         *addinfo = odr_strdup(o, p->u.elementSetName);
1067                         return 25; /* invalid esetname */
1068                     }
1069                     yaz_log(YLOG_DEBUG, "Esetname '%s' in complex compspec",
1070                          p->u.elementSetName);
1071                     espec = eset->spec;
1072                     break;
1073                 case Z_ElementSpec_externalSpec:
1074                     if (p->u.externalSpec->which == Z_External_espec1)
1075                     {
1076                         yaz_log(YLOG_DEBUG, "Got Espec-1");
1077                         espec = p->u.externalSpec-> u.espec1;
1078                     }
1079                     else
1080                     {
1081                         yaz_log(YLOG_LOG, "Unknown external espec.");
1082                         return 25; /* bad. what is proper diagnostic? */
1083                     }
1084                     break;
1085                 }
1086             }
1087         }
1088         else
1089             return 26; /* fix */
1090     }
1091     if (espec)
1092     {
1093         yaz_log(YLOG_DEBUG, "Element: Espec-1 match");
1094         return data1_doespec1(dh, n, espec);
1095     }
1096     else
1097     {
1098         yaz_log(YLOG_DEBUG, "Element: all match");
1099         return -1;
1100     }
1101 }
1102
1103 /* Add Zebra info in separate namespace ...
1104         <root 
1105          ...
1106          <metadata xmlns="http://www.indexdata.dk/zebra/">
1107           <size>359</size>
1108           <localnumber>447</localnumber>
1109           <filename>records/genera.xml</filename>
1110          </metadata>
1111         </root>
1112 */
1113
1114 static void zebra_xml_metadata (struct recRetrieveCtrl *p, data1_node *top,
1115                                 NMEM mem)
1116 {
1117     const char *idzebra_ns[3];
1118     const char *i2 = "\n  ";
1119     const char *i4 = "\n    ";
1120     data1_node *n;
1121
1122     idzebra_ns[0] = "xmlns";
1123     idzebra_ns[1] = "http://www.indexdata.dk/zebra/";
1124     idzebra_ns[2] = 0;
1125
1126     data1_mk_text (p->dh, mem, i2, top);
1127
1128     n = data1_mk_tag (p->dh, mem, "idzebra", idzebra_ns, top);
1129
1130     data1_mk_text (p->dh, mem, "\n", top);
1131
1132     data1_mk_text (p->dh, mem, i4, n);
1133     
1134     data1_mk_tag_data_int (p->dh, n, "size", p->recordSize, mem);
1135
1136     if (p->score != -1)
1137     {
1138         data1_mk_text (p->dh, mem, i4, n);
1139         data1_mk_tag_data_int (p->dh, n, "score", p->score, mem);
1140     }
1141     data1_mk_text (p->dh, mem, i4, n);
1142     data1_mk_tag_data_zint (p->dh, n, "localnumber", p->localno, mem);
1143     if (p->fname)
1144     {
1145         data1_mk_text (p->dh, mem, i4, n);
1146         data1_mk_tag_data_text(p->dh, n, "filename", p->fname, mem);
1147     }
1148     data1_mk_text (p->dh, mem, i2, n);
1149 }
1150
1151 int zebra_grs_retrieve(void *clientData, struct recRetrieveCtrl *p,
1152                        data1_node *(*grs_read)(struct grs_read_info *))
1153 {
1154     data1_node *node = 0, *onode = 0, *top;
1155     data1_node *dnew;
1156     data1_maptab *map;
1157     int res, selected = 0;
1158     NMEM mem;
1159     struct grs_read_info gri;
1160     const char *tagname;
1161
1162     int requested_schema = VAL_NONE;
1163     data1_marctab *marctab;
1164     int dummy;
1165     
1166     mem = nmem_create();
1167     gri.stream = p->stream;
1168     gri.mem = mem;
1169     gri.dh = p->dh;
1170     gri.clientData = clientData;
1171
1172     yaz_log(YLOG_DEBUG, "grs_retrieve");
1173     node = (*grs_read)(&gri);
1174     if (!node)
1175     {
1176         p->diagnostic = 14;
1177         nmem_destroy (mem);
1178         return 0;
1179     }
1180     data1_concat_text(p->dh, mem, node);
1181
1182     data1_remove_idzebra_subtree (p->dh, node);
1183
1184 #if 0
1185     data1_pr_tree (p->dh, node, stdout);
1186 #endif
1187     top = data1_get_root_tag (p->dh, node);
1188
1189     yaz_log(YLOG_DEBUG, "grs_retrieve: size");
1190     tagname = data1_systag_lookup(node->u.root.absyn, "size", "size");
1191     if (tagname &&
1192         (dnew = data1_mk_tag_data_wd(p->dh, top, tagname, mem)))
1193     {
1194         dnew->u.data.what = DATA1I_text;
1195         dnew->u.data.data = dnew->lbuf;
1196         sprintf(dnew->u.data.data, "%d", p->recordSize);
1197         dnew->u.data.len = strlen(dnew->u.data.data);
1198     }
1199     
1200     tagname = data1_systag_lookup(node->u.root.absyn, "rank", "rank");
1201     if (tagname && p->score >= 0 &&
1202         (dnew = data1_mk_tag_data_wd(p->dh, top, tagname, mem)))
1203     {
1204         yaz_log(YLOG_DEBUG, "grs_retrieve: %s", tagname);
1205         dnew->u.data.what = DATA1I_num;
1206         dnew->u.data.data = dnew->lbuf;
1207         sprintf(dnew->u.data.data, "%d", p->score);
1208         dnew->u.data.len = strlen(dnew->u.data.data);
1209     }
1210
1211     tagname = data1_systag_lookup(node->u.root.absyn, "sysno",
1212                                   "localControlNumber");
1213     if (tagname && p->localno > 0 &&
1214         (dnew = data1_mk_tag_data_wd(p->dh, top, tagname, mem)))
1215     {
1216         yaz_log(YLOG_DEBUG, "grs_retrieve: %s", tagname);
1217         dnew->u.data.what = DATA1I_text;
1218         dnew->u.data.data = dnew->lbuf;
1219         
1220         sprintf(dnew->u.data.data, ZINT_FORMAT, p->localno);
1221         dnew->u.data.len = strlen(dnew->u.data.data);
1222     }
1223
1224     if (p->input_format == VAL_TEXT_XML)
1225        zebra_xml_metadata (p, top, mem);
1226
1227 #if 0
1228     data1_pr_tree (p->dh, node, stdout);
1229 #endif
1230     if (p->comp && p->comp->which == Z_RecordComp_complex &&
1231         p->comp->u.complex->generic &&
1232         p->comp->u.complex->generic->which == Z_Schema_oid &&
1233         p->comp->u.complex->generic->schema.oid)
1234     {
1235         oident *oe = oid_getentbyoid (p->comp->u.complex->generic->schema.oid);
1236         if (oe)
1237             requested_schema = oe->value;
1238     }
1239     /* If schema has been specified, map if possible, then check that
1240      * we got the right one 
1241      */
1242     if (requested_schema != VAL_NONE)
1243     {
1244         yaz_log(YLOG_DEBUG, "grs_retrieve: schema mapping");
1245         for (map = node->u.root.absyn->maptabs; map; map = map->next)
1246         {
1247             if (map->target_absyn_ref == requested_schema)
1248             {
1249                 onode = node;
1250                 if (!(node = data1_map_record(p->dh, onode, map, mem)))
1251                 {
1252                     p->diagnostic = 14;
1253                     nmem_destroy (mem);
1254                     return 0;
1255                 }
1256                 break;
1257             }
1258         }
1259         if (node->u.root.absyn &&
1260             requested_schema != node->u.root.absyn->reference)
1261         {
1262             p->diagnostic = 238;
1263             nmem_destroy (mem);
1264             return 0;
1265         }
1266     }
1267     /*
1268      * Does the requested format match a known syntax-mapping? (this reflects
1269      * the overlap of schema and formatting which is inherent in the MARC
1270      * family)
1271      */
1272     yaz_log(YLOG_DEBUG, "grs_retrieve: syntax mapping");
1273     if (node->u.root.absyn)
1274         for (map = node->u.root.absyn->maptabs; map; map = map->next)
1275         {
1276             if (map->target_absyn_ref == p->input_format)
1277             {
1278                 onode = node;
1279                 if (!(node = data1_map_record(p->dh, onode, map, mem)))
1280                 {
1281                     p->diagnostic = 14;
1282                     nmem_destroy (mem);
1283                     return 0;
1284                 }
1285                 break;
1286             }
1287         }
1288     yaz_log(YLOG_DEBUG, "grs_retrieve: schemaIdentifier");
1289     if (node->u.root.absyn &&
1290         node->u.root.absyn->reference != VAL_NONE &&
1291         p->input_format == VAL_GRS1)
1292     {
1293         oident oe;
1294         Odr_oid *oid;
1295         int oidtmp[OID_SIZE];
1296         
1297         oe.proto = PROTO_Z3950;
1298         oe.oclass = CLASS_SCHEMA;
1299         oe.value = node->u.root.absyn->reference;
1300         
1301         if ((oid = oid_ent_to_oid (&oe, oidtmp)))
1302         {
1303             char tmp[128];
1304             data1_handle dh = p->dh;
1305             char *p = tmp;
1306             int *ii;
1307             
1308             for (ii = oid; *ii >= 0; ii++)
1309             {
1310                 if (p != tmp)
1311                         *(p++) = '.';
1312                 sprintf(p, "%d", *ii);
1313                 p += strlen(p);
1314             }
1315             if ((dnew = data1_mk_tag_data_wd(dh, top, 
1316                                              "schemaIdentifier", mem)))
1317             {
1318                 dnew->u.data.what = DATA1I_oid;
1319                 dnew->u.data.data = (char *) nmem_malloc(mem, p - tmp);
1320                 memcpy(dnew->u.data.data, tmp, p - tmp);
1321                 dnew->u.data.len = p - tmp;
1322             }
1323         }
1324     }
1325
1326     yaz_log(YLOG_DEBUG, "grs_retrieve: element spec");
1327     if (p->comp && (res = process_comp(p->dh, node, p->comp, &p->addinfo,
1328                                        p->odr)) > 0)
1329     {
1330         p->diagnostic = res;
1331         nmem_destroy(mem);
1332         return 0;
1333     }
1334     else if (p->comp && !res)
1335         selected = 1;
1336
1337 #if 0
1338     data1_pr_tree (p->dh, node, stdout);
1339 #endif
1340     yaz_log(YLOG_DEBUG, "grs_retrieve: transfer syntax mapping");
1341     switch (p->output_format = (p->input_format != VAL_NONE ?
1342                                 p->input_format : VAL_SUTRS))
1343     {
1344     case VAL_TEXT_XML:
1345 #if 0
1346         data1_pr_tree (p->dh, node, stdout);
1347 #endif
1348         /* default output encoding for XML is UTF-8 */
1349         data1_iconv (p->dh, mem, node,
1350                      p->encoding ? p->encoding : "UTF-8",
1351                      data1_get_encoding(p->dh, node));
1352
1353         if (!(p->rec_buf = data1_nodetoidsgml(p->dh, node, selected,
1354                                               &p->rec_len)))
1355             p->diagnostic = 238;
1356         else
1357         {
1358             char *new_buf = (char*) odr_malloc (p->odr, p->rec_len);
1359             memcpy (new_buf, p->rec_buf, p->rec_len);
1360             p->rec_buf = new_buf;
1361         }
1362         break;
1363     case VAL_GRS1:
1364         data1_iconv (p->dh, mem, node, "UTF-8", data1_get_encoding(p->dh, node));
1365         dummy = 0;
1366         if (!(p->rec_buf = data1_nodetogr(p->dh, node, selected,
1367                                           p->odr, &dummy)))
1368             p->diagnostic = 238; /* not available in requested syntax */
1369         else
1370             p->rec_len = -1;
1371         break;
1372     case VAL_EXPLAIN:
1373         /* ensure our data1 tree is UTF-8 */
1374         data1_iconv (p->dh, mem, node, "UTF-8", data1_get_encoding(p->dh, node));
1375         
1376         if (!(p->rec_buf = data1_nodetoexplain(p->dh, node, selected,
1377                                                p->odr)))
1378             p->diagnostic = 238;
1379         else
1380             p->rec_len = -1;
1381         break;
1382     case VAL_SUMMARY:
1383         /* ensure our data1 tree is UTF-8 */
1384         data1_iconv (p->dh, mem, node, "UTF-8", data1_get_encoding(p->dh, node));
1385         if (!(p->rec_buf = data1_nodetosummary(p->dh, node, selected,
1386                                                p->odr)))
1387             p->diagnostic = 238;
1388         else
1389             p->rec_len = -1;
1390         break;
1391     case VAL_SUTRS:
1392         if (p->encoding)
1393             data1_iconv (p->dh, mem, node, p->encoding,
1394                          data1_get_encoding(p->dh, node));
1395         if (!(p->rec_buf = data1_nodetobuf(p->dh, node, selected,
1396                                            &p->rec_len)))
1397             p->diagnostic = 238;
1398         else
1399         {
1400             char *new_buf = (char*) odr_malloc (p->odr, p->rec_len);
1401             memcpy (new_buf, p->rec_buf, p->rec_len);
1402             p->rec_buf = new_buf;
1403         }
1404         break;
1405     case VAL_SOIF:
1406         if (p->encoding)
1407             data1_iconv (p->dh, mem, node, p->encoding,
1408                          data1_get_encoding(p->dh, node));
1409         if (!(p->rec_buf = data1_nodetosoif(p->dh, node, selected,
1410                                             &p->rec_len)))
1411             p->diagnostic = 238;
1412         else
1413         {
1414             char *new_buf = (char*) odr_malloc (p->odr, p->rec_len);
1415             memcpy (new_buf, p->rec_buf, p->rec_len);
1416             p->rec_buf = new_buf;
1417         }
1418         break;
1419     default:
1420         if (!node->u.root.absyn)
1421         {
1422             p->diagnostic = 238;
1423             break;
1424         }
1425         for (marctab = node->u.root.absyn->marc; marctab;
1426              marctab = marctab->next)
1427             if (marctab->reference == p->input_format)
1428                 break;
1429         if (!marctab)
1430         {
1431             p->diagnostic = 238;
1432             break;
1433         }
1434         if (p->encoding)
1435             data1_iconv (p->dh, mem, node, p->encoding,
1436                          data1_get_encoding(p->dh, node));
1437         if (!(p->rec_buf = data1_nodetomarc(p->dh, marctab, node,
1438                                         selected, &p->rec_len)))
1439             p->diagnostic = 238;
1440         else
1441         {
1442             char *new_buf = (char*) odr_malloc (p->odr, p->rec_len);
1443             memcpy (new_buf, p->rec_buf, p->rec_len);
1444                 p->rec_buf = new_buf;
1445         }
1446     }
1447     nmem_destroy(mem);
1448     return 0;
1449 }
1450
1451 /*
1452  * Local variables:
1453  * c-basic-offset: 4
1454  * indent-tabs-mode: nil
1455  * End:
1456  * vim: shiftwidth=4 tabstop=8 expandtab
1457  */
1458