using yaz/log.h again
[idzebra-moved-to-github.git] / recctrl / marcread.c
1 /* $Id: marcread.c,v 1.28 2004-12-13 20:51:32 adam Exp $
2    Copyright (C) 1995,1996,1997,1998,1999,2000,2001,2002,2003,2004
3    Index Data Aps
4
5 This file is part of the Zebra server.
6
7 Zebra is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
11
12 Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Zebra; see the file LICENSE.zebra.  If not, write to the
19 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20 02111-1307, USA.
21 */
22
23 #include <stdio.h>
24 #include <ctype.h>
25 #include <assert.h>
26
27 #include <yaz/yaz-util.h>
28 #include <yaz/marcdisp.h>
29 #include <idzebra/recgrs.h>
30 #include "marcomp.h"
31 #include "inline.h"
32
33 #define MARC_DEBUG 0
34 #define MARCOMP_DEBUG 0
35
36 struct marc_info {
37     char type[256];
38 };
39
40 static data1_node *grs_read_iso2709 (struct grs_read_info *p, int marc_xml)
41 {
42     struct marc_info *mi = (struct marc_info*) p->clientData;
43     char buf[100000];
44     int entry_p;
45     int record_length;
46     int indicator_length;
47     int identifier_length;
48     int base_address;
49     int length_data_entry;
50     int length_starting;
51     int length_implementation;
52     int read_bytes;
53 #if MARC_DEBUG
54     FILE *outf = stdout;
55 #endif
56     data1_node *res_root, *res_top;
57     char *absynName;
58     data1_marctab *marctab;
59
60     if ((*p->readf)(p->fh, buf, 5) != 5)
61         return NULL;
62     record_length = atoi_n (buf, 5);
63     if (record_length < 25)
64     {
65         yaz_log (YLOG_WARN, "MARC record length < 25, is %d", record_length);
66         return NULL;
67     }
68     /* read remaining part - attempt to read one byte furhter... */
69     read_bytes = (*p->readf)(p->fh, buf+5, record_length-4);
70     if (read_bytes < record_length-5)
71     {
72         yaz_log (YLOG_WARN, "Couldn't read whole MARC record");
73         return NULL;
74     }
75     if (read_bytes == record_length - 4)
76     {
77         off_t cur_offset = (*p->tellf)(p->fh);
78         if (cur_offset <= 27)
79             return NULL;
80         if (p->endf)
81             (*p->endf)(p->fh, cur_offset - 1);
82     }
83     absynName = mi->type;
84     res_root = data1_mk_root (p->dh, p->mem, absynName);
85     if (!res_root)
86     {
87         yaz_log (YLOG_WARN, "cannot read MARC without an abstract syntax");
88         return 0;
89     }
90     if (marc_xml)
91     {
92         data1_node *lead;
93         const char *attr[] = { "xmlns", "http://www.loc.gov/MARC21/slim", 0};
94                          
95         res_top = data1_mk_tag (p->dh, p->mem, "record", attr, res_root);
96
97         lead = data1_mk_tag(p->dh, p->mem, "leader", 0, res_top);
98         data1_mk_text_n(p->dh, p->mem, buf, 24, lead);
99     }
100     else
101         res_top = data1_mk_tag (p->dh, p->mem, absynName, 0, res_root);
102
103     if ((marctab = data1_absyn_getmarctab(p->dh, res_root->u.root.absyn)))
104     {
105         memcpy(marctab->leader, buf, 24);
106         memcpy(marctab->implementation_codes, buf+6, 4);
107         marctab->implementation_codes[4] = '\0';
108         memcpy(marctab->user_systems, buf+17, 3);
109         marctab->user_systems[3] = '\0';
110     }
111
112     if (marctab && marctab->force_indicator_length >= 0)
113         indicator_length = marctab->force_indicator_length;
114     else
115         indicator_length = atoi_n (buf+10, 1);
116     if (marctab && marctab->force_identifier_length >= 0)
117         identifier_length = marctab->force_identifier_length;
118     else
119         identifier_length = atoi_n (buf+11, 1);
120     base_address = atoi_n (buf+12, 5);
121
122     length_data_entry = atoi_n (buf+20, 1);
123     length_starting = atoi_n (buf+21, 1);
124     length_implementation = atoi_n (buf+22, 1);
125
126     for (entry_p = 24; buf[entry_p] != ISO2709_FS; )
127         entry_p += 3+length_data_entry+length_starting;
128     base_address = entry_p+1;
129     for (entry_p = 24; buf[entry_p] != ISO2709_FS; )
130     {
131         int data_length;
132         int data_offset;
133         int end_offset;
134         int i, i0;
135         char tag[4];
136         data1_node *res;
137         data1_node *parent = res_top;
138
139         memcpy (tag, buf+entry_p, 3);
140         entry_p += 3;
141         tag[3] = '\0';
142
143         if (marc_xml)
144             res = parent;
145         else
146             res = data1_mk_tag_n (p->dh, p->mem, tag, 3, 0 /* attr */, parent);
147         
148 #if MARC_DEBUG
149         fprintf (outf, "%s ", tag);
150 #endif
151         data_length = atoi_n (buf+entry_p, length_data_entry);
152         entry_p += length_data_entry;
153         data_offset = atoi_n (buf+entry_p, length_starting);
154         entry_p += length_starting;
155         i = data_offset + base_address;
156         end_offset = i+data_length-1;
157
158         if (memcmp (tag, "00", 2) && indicator_length)
159         {
160             /* generate indicator node */
161             if (marc_xml)
162             {
163                 const char *attr[10];
164                 int j;
165
166                 attr[0] = "tag";
167                 attr[1] = tag;
168                 attr[2] = 0;
169
170                 res = data1_mk_tag(p->dh, p->mem, "datafield", attr, res);
171
172                 for (j = 0; j<indicator_length; j++)
173                 {
174                     char str1[18], str2[2];
175                     sprintf (str1, "ind%d", j+1);
176                     str2[0] = buf[i+j];
177                     str2[1] = '\0';
178
179                     attr[0] = str1;
180                     attr[1] = str2;
181                     
182                     data1_tag_add_attr (p->dh, p->mem, res, attr);
183                 }
184             }
185             else
186             {
187 #if MARC_DEBUG
188                 int j;
189 #endif
190                 res = data1_mk_tag_n (p->dh, p->mem, 
191                                       buf+i, indicator_length, 0 /* attr */, res);
192 #if MARC_DEBUG
193                 for (j = 0; j<indicator_length; j++)
194                     fprintf (outf, "%c", buf[j+i]);
195 #endif
196             }
197             i += indicator_length;
198         } 
199         else
200         {
201             if (marc_xml)
202             {
203                 const char *attr[10];
204                 
205                 attr[0] = "tag";
206                 attr[1] = tag;
207                 attr[2] = 0;
208                 
209                 res = data1_mk_tag(p->dh, p->mem, "controlfield", attr, res);
210             }
211         }
212         parent = res;
213         /* traverse sub fields */
214         i0 = i;
215         while (buf[i] != ISO2709_RS && buf[i] != ISO2709_FS && i < end_offset)
216         {
217             if (memcmp (tag, "00", 2) && identifier_length)
218             {
219                 data1_node *res;
220                 if (marc_xml)
221                 {
222                     int j;
223                     const char *attr[3];
224                     char code[10];
225                     
226                     for (j = 1; j<identifier_length && j < 9; j++)
227                         code[j-1] = buf[i+j];
228                     code[j-1] = 0;
229                     attr[0] = "code";
230                     attr[1] = code;
231                     attr[2] = 0;
232                     res = data1_mk_tag(p->dh, p->mem, "subfield",
233                                        attr, parent);
234                 }
235                 else
236                 {
237                     res = data1_mk_tag_n (p->dh, p->mem,
238                                            buf+i+1, identifier_length-1, 
239                                            0 /* attr */, parent);
240                 }
241 #if MARC_DEBUG
242                 fprintf (outf, " $"); 
243                 for (j = 1; j<identifier_length; j++)
244                     fprintf (outf, "%c", buf[j+i]);
245                 fprintf (outf, " ");
246 #endif
247                 i += identifier_length;
248                 i0 = i;
249                 while (buf[i] != ISO2709_RS && buf[i] != ISO2709_IDFS &&
250                      buf[i] != ISO2709_FS && i < end_offset)
251                 {
252 #if MARC_DEBUG
253                     fprintf (outf, "%c", buf[i]);
254 #endif
255                     i++;
256                 }
257                 data1_mk_text_n (p->dh, p->mem, buf + i0, i - i0, res);
258                 i0 = i;
259             }
260             else
261             {
262 #if MARC_DEBUG
263                 fprintf (outf, "%c", buf[i]);
264 #endif
265                 i++;
266             }
267         }
268         if (i > i0)
269         {
270             data1_mk_text_n (p->dh, p->mem, buf + i0, i - i0, parent);
271         }
272 #if MARC_DEBUG
273         fprintf (outf, "\n");
274         if (i < end_offset)
275             fprintf (outf, "-- separator but not at end of field\n");
276         if (buf[i] != ISO2709_RS && buf[i] != ISO2709_FS)
277             fprintf (outf, "-- no separator at end of field\n");
278 #endif
279     }
280     return res_root;
281 }
282
283 /*
284  * Locate some data under this node. This routine should handle variants
285  * prettily.
286  */
287 static char *get_data(data1_node *n, int *len)
288 {
289     char *r;
290
291     while (n)
292     {
293         if (n->which == DATA1N_data)
294         {
295             int i;
296             *len = n->u.data.len;
297
298             for (i = 0; i<*len; i++)
299                 if (!d1_isspace(n->u.data.data[i]))
300                     break;
301             while (*len && d1_isspace(n->u.data.data[*len - 1]))
302                 (*len)--;
303             *len = *len - i;
304             if (*len > 0)
305                 return n->u.data.data + i;
306         }
307         if (n->which == DATA1N_tag)
308             n = n->child;
309         else if (n->which == DATA1N_data)
310             n = n->next;
311         else
312             break;      
313     }
314     r = "";
315     *len = strlen(r);
316     return r;
317 }
318
319 static data1_node *lookup_subfield(data1_node *node, const char *name)
320 {
321     data1_node *p;
322     
323     for (p=node; p; p=p->next)
324     {
325         if (!yaz_matchstr(p->u.tag.tag, name))
326             return p;
327     }
328     return 0;
329 }
330
331 static inline_subfield *lookup_inline_subfield(inline_subfield *pisf,
332                                                const char *name)
333 {
334     inline_subfield *p;
335     
336     for (p=pisf; p; p=p->next)
337     {
338         if (!yaz_matchstr(p->name, name))
339             return p;
340     }
341     return 0;
342 }
343
344 static inline_subfield *cat_inline_subfield(mc_subfield *psf, WRBUF buf,
345                                             inline_subfield *pisf)
346 {
347     mc_subfield *p;
348     
349     for (p = psf; p && pisf; p = p->next)
350     {
351         if (p->which == MC_SF)
352         {
353             inline_subfield *found = lookup_inline_subfield(pisf, p->name);
354             
355             if (found)
356             {
357                 if (strcmp(p->prefix, "_"))
358                 {
359                     wrbuf_puts(buf, " ");
360                     wrbuf_puts(buf, p->prefix);
361                 }
362                 if (p->interval.start == -1)
363                 {
364                     wrbuf_puts(buf, found->data);
365                 }
366                 else
367                 {
368                     wrbuf_write(buf, found->data+p->interval.start,
369                                 p->interval.end-p->interval.start);
370                     wrbuf_puts(buf, "");
371                 }
372                 if (strcmp(p->suffix, "_"))
373                 {
374                     wrbuf_puts(buf, p->suffix);
375                     wrbuf_puts(buf, " ");
376                 }
377 #if MARCOMP_DEBUG
378                 yaz_log(YLOG_LOG, "cat_inline_subfield(): add subfield $%s", found->name);
379 #endif          
380                 pisf = found->next;
381             }
382         }
383         else if (p->which == MC_SFVARIANT)
384         {
385             inline_subfield *next;
386             
387             do {
388                 next = cat_inline_subfield(p->u.child, buf, pisf);
389                 if (next == pisf)
390                     break;
391                 pisf = next;
392             } while (pisf);
393         }
394         else if (p->which == MC_SFGROUP)
395         {
396             mc_subfield *pp;
397             int found;
398             
399             for (pp = p->u.child, found = 0; pp; pp = pp->next)
400             {
401                 if (!yaz_matchstr(pisf->name, p->name))
402                 {
403                     found = 1;
404                     break;
405                 }
406             }
407             if (found)
408             {
409                 wrbuf_puts(buf, " (");
410                 pisf = cat_inline_subfield(p->u.child, buf, pisf);
411                 wrbuf_puts(buf, ") ");
412             }
413         }
414     }
415     return pisf; 
416 }
417
418 static void cat_inline_field(mc_field *pf, WRBUF buf, data1_node *subfield)
419 {    
420     if (!pf || !subfield)
421         return;
422
423     for (;subfield;)
424     {
425         int len;
426         inline_field *pif=NULL;
427         data1_node *psubf;
428         
429         if (yaz_matchstr(subfield->u.tag.tag, "1"))
430         {
431             subfield = subfield->next;
432             continue;
433         }
434         
435         psubf = subfield;
436         pif = inline_mk_field();
437         do
438         {
439             int i;
440             if ((i=inline_parse(pif, psubf->u.tag.tag, get_data(psubf, &len)))<0)
441             {
442                 yaz_log(YLOG_WARN, "inline subfield ($%s): parse error",
443                     psubf->u.tag.tag);
444                 inline_destroy_field(pif);
445                 return; 
446             }
447             psubf = psubf->next;
448         } while (psubf && yaz_matchstr(psubf->u.tag.tag, "1"));
449         
450         subfield = psubf;
451         
452         if (pif && !yaz_matchstr(pif->name, pf->name))
453         {
454             if (!pf->list && pif->list)
455             {
456                 wrbuf_puts(buf, pif->list->data);
457             }
458             else
459             {
460                 int ind1, ind2;
461
462                 /*
463                     check indicators
464                 */
465
466                 ind1 = (pif->ind1[0] == ' ') ? '_':pif->ind1[0];
467                 ind2 = (pif->ind2[0] == ' ') ? '_':pif->ind2[0];
468     
469                 if (((pf->ind1[0] == '.') || (ind1 == pf->ind1[0])) &&
470                     ((pf->ind2[0] == '.') || (ind2 == pf->ind2[0])))
471                 {
472                     cat_inline_subfield(pf->list, buf, pif->list);
473                     
474                     /*
475                         add separator for inline fields
476                     */
477                     if (wrbuf_len(buf))
478                     {
479                         wrbuf_puts(buf, "\n");
480                     }
481                 }
482                 else
483                 {
484                     yaz_log(YLOG_WARN, "In-line field %s missed -- indicators do not match", pif->name);
485                 }
486             }
487         }
488         inline_destroy_field(pif);
489     }
490 #if MARCOMP_DEBUG    
491     yaz_log(YLOG_LOG, "cat_inline_field(): got buffer {%s}", buf);
492 #endif
493 }
494
495 static data1_node *cat_subfield(mc_subfield *psf, WRBUF buf,
496                                 data1_node *subfield)
497 {
498     mc_subfield *p;
499     
500     for (p = psf; p && subfield; p = p->next)
501     {
502         if (p->which == MC_SF)
503         {
504             data1_node *found = lookup_subfield(subfield, p->name);
505             
506             if (found)
507             {
508                 int len;
509                 
510                 if (strcmp(p->prefix, "_"))
511                 {
512                     wrbuf_puts(buf, " ");
513                     wrbuf_puts(buf, p->prefix);
514                 }
515                 
516                 if (p->u.in_line)
517                 {
518                     cat_inline_field(p->u.in_line, buf, found);
519                 }
520                 else if (p->interval.start == -1)
521                 {
522                     wrbuf_puts(buf, get_data(found, &len));
523                 }
524                 else
525                 {
526                     wrbuf_write(buf, get_data(found, &len)+p->interval.start,
527                         p->interval.end-p->interval.start);
528                     wrbuf_puts(buf, "");
529                 }
530                 if (strcmp(p->suffix, "_"))
531                 {
532                     wrbuf_puts(buf, p->suffix);
533                     wrbuf_puts(buf, " ");
534                 }
535 #if MARCOMP_DEBUG               
536                 yaz_log(YLOG_LOG, "cat_subfield(): add subfield $%s", found->u.tag.tag);
537 #endif          
538                 subfield = found->next;
539             }
540         }
541         else if (p->which == MC_SFVARIANT)
542         {
543             data1_node *next;
544             do {
545                 next = cat_subfield(p->u.child, buf, subfield);
546                 if (next == subfield)
547                     break;
548                 subfield = next;
549             } while (subfield);
550         }
551         else if (p->which == MC_SFGROUP)
552         {
553             mc_subfield *pp;
554             int found;
555             
556             for (pp = p->u.child, found = 0; pp; pp = pp->next)
557             {
558                 if (!yaz_matchstr(subfield->u.tag.tag, pp->name))
559                 {
560                     found = 1;
561                     break;
562                 }
563             }
564             if (found)
565             {
566                 wrbuf_puts(buf, " (");
567                 subfield = cat_subfield(p->u.child, buf, subfield);
568                 wrbuf_puts(buf, ") ");
569             }
570         }
571     }
572     return subfield;
573 }
574
575 static data1_node *cat_field(struct grs_read_info *p, mc_field *pf,
576                              WRBUF buf, data1_node *field)
577 {
578     data1_node *subfield;
579     int ind1, ind2;
580     
581     if (!pf || !field)
582         return 0;
583
584     
585     if (yaz_matchstr(field->u.tag.tag, pf->name))
586         return field->next;
587
588     subfield = field->child;
589     
590     if (!subfield)
591         return field->next;
592
593     /*
594         check subfield without indicators
595     */
596     
597     if (!pf->list && subfield->which == DATA1N_data)
598     {
599         int len;
600         
601         if (pf->interval.start == -1)
602         {
603             wrbuf_puts(buf, get_data(field, &len));
604         }
605         else
606         {
607             wrbuf_write(buf, get_data(field, &len)+pf->interval.start,
608                         pf->interval.end-pf->interval.start);
609             wrbuf_puts(buf, "");
610         }
611 #if MARCOMP_DEBUG
612         yaz_log(YLOG_LOG, "cat_field(): got buffer {%s}", buf);
613 #endif
614         return field->next;
615     }
616     
617     /*
618         check indicators
619     */
620
621     ind1 = (subfield->u.tag.tag[0] == ' ') ? '_':subfield->u.tag.tag[0];
622     ind2 = (subfield->u.tag.tag[1] == ' ') ? '_':subfield->u.tag.tag[1];
623     
624     if (!(
625         ((pf->ind1[0] == '.') || (ind1 == pf->ind1[0])) &&
626         ((pf->ind2[0] == '.') || (ind2 == pf->ind2[0]))
627         ))
628     {
629 #if MARCOMP_DEBUG
630         yaz_log(YLOG_WARN, "Field %s missed -- does not match indicators", field->u.tag.tag);
631 #endif
632         return field->next;
633     }
634     
635     subfield = subfield->child;
636     
637     if (!subfield)
638         return field->next;
639
640     cat_subfield(pf->list, buf, subfield);
641
642 #if MARCOMP_DEBUG    
643     yaz_log(YLOG_LOG, "cat_field(): got buffer {%s}", buf);
644 #endif
645     
646     return field->next;    
647 }
648
649 static int is_empty(char *s)
650 {
651     char *p = s;
652     
653     for (p = s; *p; p++)
654     {
655         if (!isspace(*p))
656             return 0;
657     }
658     return 1;
659 }
660
661 static void parse_data1_tree(struct grs_read_info *p, const char *mc_stmnt,
662                              data1_node *root)
663 {
664     data1_marctab *marctab = data1_absyn_getmarctab(p->dh, root->u.root.absyn);
665     data1_node *top = root->child;
666     data1_node *field;
667     mc_context *c;
668     mc_field *pf;
669     WRBUF buf;
670     
671     c = mc_mk_context(mc_stmnt+3);
672     
673     if (!c)
674         return;
675         
676     pf = mc_getfield(c);
677     
678     if (!pf)
679     {
680         mc_destroy_context(c);
681         return;
682     }
683     buf = wrbuf_alloc();
684 #if MARCOMP_DEBUG    
685     yaz_log(YLOG_LOG, "parse_data1_tree(): statement -{%s}", mc_stmnt);
686 #endif
687     if (!yaz_matchstr(pf->name, "ldr"))
688     {
689         data1_node *new;
690 #if MARCOMP_DEBUG
691         yaz_log(YLOG_LOG,"parse_data1_tree(): try LEADER from {%d} to {%d} positions",
692             pf->interval.start, pf->interval.end);
693 #endif  
694         new = data1_mk_tag_n(p->dh, p->mem, mc_stmnt, strlen(mc_stmnt), 0, top);
695         data1_mk_text_n(p->dh, p->mem, marctab->leader+pf->interval.start,
696             pf->interval.end-pf->interval.start+1, new);
697     }
698     else
699     {
700         field=top->child;
701         
702         while(field)
703         {
704             if (!yaz_matchstr(field->u.tag.tag, pf->name))
705             {
706                 data1_node *new;
707                 char *pb;
708 #if MARCOMP_DEBUG               
709                 yaz_log(YLOG_LOG, "parse_data1_tree(): try field {%s}", field->u.tag.tag);
710 #endif          
711                 wrbuf_rewind(buf);
712                 wrbuf_puts(buf, "");
713
714                 field = cat_field(p, pf, buf, field);
715                 
716                 pb = wrbuf_buf(buf);
717                 for (pb = strtok(pb, "\n"); pb; pb = strtok(NULL, "\n"))
718                 {
719                         if (!is_empty(pb))
720                         {
721                                 new = data1_mk_tag_n(p->dh, p->mem, mc_stmnt, strlen(mc_stmnt), 0, top);
722                                 data1_mk_text_n(p->dh, p->mem, pb, strlen(pb), new);
723                         }
724                 }
725             }
726             else
727             {
728                 field = field->next;
729             }
730         }
731     }
732     mc_destroy_field(pf);
733     mc_destroy_context(c);
734     wrbuf_free(buf, 1);
735 }
736
737 data1_node *grs_read_marcxml(struct grs_read_info *p)
738 {
739     data1_node *root = grs_read_iso2709(p, 1);
740     data1_element *e;
741
742     if (!root)
743         return 0;
744         
745     for (e = data1_absyn_getelements(p->dh, root->u.root.absyn); e; e=e->next)
746     {
747         data1_tag *tag = e->tag;
748         
749         if (tag && tag->which == DATA1T_string &&
750             !yaz_matchstr(tag->value.string, "mc?"))
751                 parse_data1_tree(p, tag->value.string, root);
752     }
753     return root;
754 }
755
756 data1_node *grs_read_marc(struct grs_read_info *p)
757 {
758     data1_node *root = grs_read_iso2709(p, 0);
759     data1_element *e;
760
761     if (!root)
762         return 0;
763         
764     for (e = data1_absyn_getelements(p->dh, root->u.root.absyn); e; e=e->next)
765     {
766         data1_tag *tag = e->tag;
767         
768         if (tag && tag->which == DATA1T_string &&
769             !yaz_matchstr(tag->value.string, "mc?"))
770                 parse_data1_tree(p, tag->value.string, root);
771     }
772     return root;
773 }
774
775 static void *init_marc(Res res, RecType rt)
776 {
777     struct marc_info *p = xmalloc(sizeof(*p));
778     strcpy(p->type, "");
779     return p;
780 }
781
782 static void config_marc(void *clientData, Res res, const char *args)
783 {
784     struct marc_info *p = (struct marc_info*) clientData;
785     if (strlen(args) < sizeof(p->type))
786         strcpy(p->type, args);
787 }
788
789 static void destroy_marc(void *clientData)
790 {
791     struct marc_info *p = (struct marc_info*) clientData;
792     xfree (p);
793 }
794
795
796 static int extract_marc(void *clientData, struct recExtractCtrl *ctrl)
797 {
798     return zebra_grs_extract(clientData, ctrl, grs_read_marc);
799 }
800
801 static int retrieve_marc(void *clientData, struct recRetrieveCtrl *ctrl)
802 {
803     return zebra_grs_retrieve(clientData, ctrl, grs_read_marc);
804 }
805
806 static struct recType marc_type = {
807     "grs.marc",
808     init_marc,
809     config_marc,
810     destroy_marc,
811     extract_marc,
812     retrieve_marc,
813 };
814
815 static int extract_marcxml(void *clientData, struct recExtractCtrl *ctrl)
816 {
817     return zebra_grs_extract(clientData, ctrl, grs_read_marcxml);
818 }
819
820 static int retrieve_marcxml(void *clientData, struct recRetrieveCtrl *ctrl)
821 {
822     return zebra_grs_retrieve(clientData, ctrl, grs_read_marcxml);
823 }
824
825 static struct recType marcxml_type = {
826     "grs.marcxml",
827     init_marc,
828     config_marc,
829     destroy_marc,
830     extract_marcxml,
831     retrieve_marcxml,
832 };
833
834 RecType
835 #ifdef IDZEBRA_STATIC_GRS_MARC
836 idzebra_filter_grs_marc
837 #else
838 idzebra_filter
839 #endif
840
841 [] = {
842     &marc_type,
843     &marcxml_type,
844     0,
845 };
846