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