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