6c7ba4c8286a1b89d1057524493e097a33cc49e2
[yaz-moved-to-github.git] / src / marcdisp.c
1 /*
2  * Copyright (C) 1995-2005, Index Data ApS
3  * See the file LICENSE for details.
4  *
5  * $Id: marcdisp.c,v 1.16 2005-02-08 13:51:30 adam Exp $
6  */
7
8 /**
9  * \file marcdisp.c
10  * \brief Implements MARC display - and conversion utilities
11  */
12
13 #if HAVE_CONFIG_H
14 #include <config.h>
15 #endif
16
17 #include <stdio.h>
18 #include <string.h>
19 #include <ctype.h>
20 #include <yaz/marcdisp.h>
21 #include <yaz/wrbuf.h>
22 #include <yaz/yaz-util.h>
23
24 struct yaz_marc_t_ {
25     WRBUF m_wr;
26     int xml;
27     int debug;
28     yaz_iconv_t iconv_cd;
29     char subfield_str[8];
30     char endline_str[8];
31 };
32
33 yaz_marc_t yaz_marc_create(void)
34 {
35     yaz_marc_t mt = (yaz_marc_t) xmalloc(sizeof(*mt));
36     mt->xml = YAZ_MARC_LINE;
37     mt->debug = 0;
38     mt->m_wr = wrbuf_alloc();
39     mt->iconv_cd = 0;
40     strcpy(mt->subfield_str, " $");
41     strcpy(mt->endline_str, "\n");
42     return mt;
43 }
44
45 void yaz_marc_subfield_str(yaz_marc_t mt, const char *s)
46 {
47     strncpy(mt->subfield_str, s, sizeof(mt->subfield_str)-1);
48     mt->subfield_str[sizeof(mt->subfield_str)-1] = '\0';
49 }
50
51 void yaz_marc_endline_str(yaz_marc_t mt, const char *s)
52 {
53     strncpy(mt->endline_str, s, sizeof(mt->endline_str)-1);
54     mt->endline_str[sizeof(mt->endline_str)-1] = '\0';
55 }
56
57 void yaz_marc_destroy(yaz_marc_t mt)
58 {
59     if (!mt)
60         return ;
61     wrbuf_free (mt->m_wr, 1);
62     xfree (mt);
63 }
64
65 static void marc_cdata (yaz_marc_t mt, const char *buf, size_t len, WRBUF wr)
66 {
67     if (mt->xml == YAZ_MARC_ISO2709)
68         wrbuf_iconv_write(wr, mt->iconv_cd, buf, len);
69     else if (mt->xml == YAZ_MARC_LINE)
70         wrbuf_iconv_write(wr, mt->iconv_cd, buf, len);
71     else
72         wrbuf_iconv_write_cdata(wr, mt->iconv_cd, buf, len);
73 }
74
75 static int atoi_n_check(const char *buf, int size, int *val)
76 {
77     if (!isdigit(*(const unsigned char *) buf))
78         return 0;
79     *val = atoi_n(buf, size);
80     return 1;
81 }
82
83 int yaz_marc_decode_wrbuf (yaz_marc_t mt, const char *buf, int bsize, WRBUF wr)
84 {
85     int entry_p;
86     int record_length;
87     int indicator_length;
88     int identifier_length;
89     int base_address;
90     int length_data_entry;
91     int length_starting;
92     int length_implementation;
93     char lead[24];
94     int produce_warnings = 0;
95
96     if (mt->debug)
97         produce_warnings = 1;
98     if (mt->xml == YAZ_MARC_SIMPLEXML || mt->xml == YAZ_MARC_OAIMARC
99         || mt->xml == YAZ_MARC_MARCXML || mt->xml == YAZ_MARC_XCHANGE)
100         produce_warnings = 1;
101
102     wrbuf_rewind(wr);
103
104     record_length = atoi_n (buf, 5);
105     if (record_length < 25)
106     {
107         if (mt->debug)
108         {
109             char str[40];
110             
111             wrbuf_printf(wr, "<!-- Record length %d - aborting -->\n",
112                             record_length);
113         }
114         return -1;
115     }
116     memcpy(lead, buf, 24);  /* se can modify the header for output */
117
118     /* ballout if bsize is known and record_length is less than that */
119     if (bsize != -1 && record_length > bsize)
120         return -1;
121     if (!atoi_n_check(buf+10, 1, &indicator_length))
122     {
123         if (produce_warnings)
124             wrbuf_printf(wr, "<!-- Indicator length at offset 10 should hold a digit. Assuming 2 -->\n");
125         lead[10] = '2';
126         indicator_length = 2;
127     }
128     if (!atoi_n_check(buf+11, 1, &identifier_length))
129     {
130         if (produce_warnings)
131             wrbuf_printf(wr, "<!-- Identifier length at offset 11 should hold a digit. Assuming 2 -->\n");
132         lead[11] = '2';
133         identifier_length = 2;
134     }
135     if (!atoi_n_check(buf+12, 5, &base_address))
136     {
137         if (produce_warnings)
138             wrbuf_printf(wr, "<!-- Base address at offsets 12..16 should hold a number. Assuming 0 -->\n");
139         base_address = 0;
140     }
141     if (!atoi_n_check(buf+20, 1, &length_data_entry))
142     {
143         if (produce_warnings)
144             wrbuf_printf(wr, "<!-- Length data entry at offset 20 should hold a digit. Assuming 4 -->\n");
145         length_data_entry = 4;
146         lead[20] = '4';
147     }
148     if (!atoi_n_check(buf+21, 1, &length_starting))
149     {
150         if (produce_warnings)
151             wrbuf_printf(wr, "<!-- Length starting at offset 21 should hold a digit. Assuming 5 -->\n");
152         length_starting = 5;
153         lead[21] = '5';
154     }
155     if (!atoi_n_check(buf+22, 1, &length_implementation))
156     {
157         if (produce_warnings)
158             wrbuf_printf(wr, "<!-- Length implementation at offset 22 should hold a digit. Assuming 0 -->\n");
159         length_implementation = 0;
160         lead[22] = '0';
161     }
162
163     if (mt->xml != YAZ_MARC_LINE)
164     {
165         char str[80];
166         int i;
167         switch(mt->xml)
168         {
169         case YAZ_MARC_ISO2709:
170             break;
171         case YAZ_MARC_SIMPLEXML:
172             wrbuf_puts (wr, "<iso2709\n");
173             sprintf (str, " RecordStatus=\"%c\"\n", buf[5]);
174             wrbuf_puts (wr, str);
175             sprintf (str, " TypeOfRecord=\"%c\"\n", buf[6]);
176             wrbuf_puts (wr, str);
177             for (i = 1; i<=19; i++)
178             {
179                 sprintf (str, " ImplDefined%d=\"%c\"\n", i, buf[6+i]);
180                 wrbuf_puts (wr, str);
181             }
182             wrbuf_puts (wr, ">\n");
183             break;
184         case YAZ_MARC_OAIMARC:
185             wrbuf_puts(
186                 wr,
187                 "<oai_marc xmlns=\"http://www.openarchives.org/OIA/oai_marc\""
188                 "\n"
189                 " xmlns:xsi=\"http://www.w3.org/2000/10/XMLSchema-instance\""
190                 "\n"
191                 " xsi:schemaLocation=\"http://www.openarchives.org/OAI/oai_marc.xsd\""
192                 "\n"
193                 );
194             
195             sprintf (str, " status=\"%c\" type=\"%c\" catForm=\"%c\">\n",
196                      buf[5], buf[6], buf[7]);
197             wrbuf_puts (wr, str);
198             break;
199         case YAZ_MARC_MARCXML:
200             wrbuf_printf(
201                 wr,
202                 "<record xmlns=\"http://www.loc.gov/MARC21/slim\">\n"
203                 "  <leader>");
204             lead[9] = 'a';                 /* set leader to signal unicode */
205             marc_cdata(mt, lead, 24, wr); 
206             wrbuf_printf(wr, "</leader>\n");
207             break;
208         case YAZ_MARC_XCHANGE:
209             wrbuf_printf(
210                 wr,
211                 "<record xmlns=\"http://www.bs.dk/standards/MarcXchange\">\n"
212                 "  <leader>");
213             marc_cdata(mt, lead, 24, wr);
214             wrbuf_printf(wr, "</leader>\n");
215             break;
216         }
217     }
218     if (mt->debug)
219     {
220         char str[40];
221
222         if (mt->xml)
223             wrbuf_puts (wr, "<!--\n");
224         sprintf (str, "Record length         %5d\n", record_length);
225         wrbuf_puts (wr, str);
226         sprintf (str, "Indicator length      %5d\n", indicator_length);
227         wrbuf_puts (wr, str);
228         sprintf (str, "Identifier length     %5d\n", identifier_length);
229         wrbuf_puts (wr, str);
230         sprintf (str, "Base address          %5d\n", base_address);
231         wrbuf_puts (wr, str);
232         sprintf (str, "Length data entry     %5d\n", length_data_entry);
233         wrbuf_puts (wr, str);
234         sprintf (str, "Length starting       %5d\n", length_starting);
235         wrbuf_puts (wr, str);
236         sprintf (str, "Length implementation %5d\n", length_implementation);
237         wrbuf_puts (wr, str);
238         if (mt->xml)
239             wrbuf_puts (wr, "-->\n");
240     }
241
242     /* first pass. determine length of directory & base of data */
243     for (entry_p = 24; buf[entry_p] != ISO2709_FS; )
244     {
245         entry_p += 3+length_data_entry+length_starting;
246         if (entry_p >= record_length)
247             return -1;
248     }
249     if (mt->debug && base_address != entry_p+1)
250     {
251         if (produce_warnings)
252             wrbuf_printf (wr,"  <!-- base address not at end of directory "
253                           "base=%d end=%d -->\n", base_address, entry_p+1);
254     }
255     base_address = entry_p+1;
256
257     if (mt->xml == YAZ_MARC_ISO2709)
258     {
259         WRBUF wr_head = wrbuf_alloc();
260         WRBUF wr_dir = wrbuf_alloc();
261         WRBUF wr_tmp = wrbuf_alloc();
262
263         int data_p = 0;
264         /* second pass. create directory for ISO2709 output */
265         for (entry_p = 24; buf[entry_p] != ISO2709_FS; )
266         {
267             int data_length, data_offset, end_offset;
268             int i, sz1, sz2;
269             
270             wrbuf_write(wr_dir, buf+entry_p, 3);
271             entry_p += 3;
272             
273             data_length = atoi_n (buf+entry_p, length_data_entry);
274             entry_p += length_data_entry;
275             data_offset = atoi_n (buf+entry_p, length_starting);
276             entry_p += length_starting;
277             i = data_offset + base_address;
278             end_offset = i+data_length-1;
279             
280             while (buf[i] != ISO2709_RS && buf[i] != ISO2709_FS &&
281                    i < end_offset)
282                 i++;
283             sz1 = 1+i - (data_offset + base_address);
284             if (mt->iconv_cd)
285             {
286                 sz2 = wrbuf_iconv_write(wr_tmp, mt->iconv_cd,
287                                         buf + data_offset+base_address, sz1);
288                 wrbuf_rewind(wr_tmp);
289             }
290             else
291                 sz2 = sz1;
292             wrbuf_printf(wr_dir, "%0*d", length_data_entry, sz2);
293             wrbuf_printf(wr_dir, "%0*d", length_starting, data_p);
294             data_p += sz2;
295         }
296         wrbuf_putc(wr_dir, ISO2709_FS);
297         wrbuf_printf(wr_head, "%05d", data_p+1 + base_address);
298         wrbuf_write(wr_head, lead+5, 7);
299         wrbuf_printf(wr_head, "%05d", base_address);
300         wrbuf_write(wr_head, lead+17, 7);
301
302         wrbuf_write(wr, wrbuf_buf(wr_head), 24);
303         wrbuf_write(wr, wrbuf_buf(wr_dir), wrbuf_len(wr_dir));
304         wrbuf_free(wr_head, 1);
305         wrbuf_free(wr_dir, 1);
306         wrbuf_free(wr_tmp, 1);
307     }
308     /* third pass. create data output */
309     for (entry_p = 24; buf[entry_p] != ISO2709_FS; )
310     {
311         int data_length;
312         int data_offset;
313         int end_offset;
314         int i, j;
315         char tag[4];
316         int identifier_flag = 0;
317         int entry_p0;
318
319         memcpy (tag, buf+entry_p, 3);
320         entry_p += 3;
321         entry_p0 = entry_p;
322         tag[3] = '\0';
323         data_length = atoi_n (buf+entry_p, length_data_entry);
324         entry_p += length_data_entry;
325         data_offset = atoi_n (buf+entry_p, length_starting);
326         entry_p += length_starting;
327         i = data_offset + base_address;
328         end_offset = i+data_length-1;
329         
330         if (mt->debug)
331         {
332             wrbuf_printf(wr, "<!-- offset=%d data dlength=%d doffset=%d -->\n",
333                     entry_p0, data_length, data_offset);
334         }
335         
336         if (memcmp (tag, "00", 2))
337             identifier_flag = 1;  /* if not 00X assume subfields */
338         else if (indicator_length < 4 && indicator_length > 0)
339         {
340             /* Danmarc 00X have subfields */
341             if (buf[i + indicator_length] == ISO2709_IDFS)
342                 identifier_flag = 1;
343             else if (buf[i + indicator_length + 1] == ISO2709_IDFS)
344                 identifier_flag = 2;
345         }
346
347         if (mt->debug)
348         {
349             wrbuf_printf(wr, "<!-- identifier_flag = %d -->\n",
350                          identifier_flag);
351         } 
352        
353         switch(mt->xml)
354         {
355         case YAZ_MARC_LINE:
356             if (mt->debug)
357                 wrbuf_puts (wr, "Tag: ");
358             wrbuf_puts (wr, tag);
359             wrbuf_puts (wr, " ");
360             break;
361         case YAZ_MARC_SIMPLEXML:
362             wrbuf_printf (wr, "<field tag=\"");
363             marc_cdata(mt, tag, strlen(tag), wr);
364             wrbuf_printf(wr, "\"");
365             break;
366         case YAZ_MARC_OAIMARC:
367             if (identifier_flag)
368                 wrbuf_printf (wr, "  <varfield id=\"");
369             else
370                 wrbuf_printf (wr, "  <fixfield id=\"");
371             marc_cdata(mt, tag, strlen(tag), wr);
372             wrbuf_printf(wr, "\"");
373             break;
374         case YAZ_MARC_MARCXML:
375         case YAZ_MARC_XCHANGE:
376             if (identifier_flag)
377                 wrbuf_printf (wr, "  <datafield tag=\"");
378             else
379                 wrbuf_printf (wr, "  <controlfield tag=\"");
380             marc_cdata(mt, tag, strlen(tag), wr);
381             wrbuf_printf(wr, "\"");
382         }
383         
384         if (identifier_flag)
385         {
386             i += identifier_flag-1;
387             for (j = 0; j<indicator_length; j++, i++)
388             {
389                 switch(mt->xml)
390                 {
391                 case YAZ_MARC_ISO2709:
392                     wrbuf_putc(wr, buf[i]);
393                     break;
394                 case YAZ_MARC_LINE:
395                     if (mt->debug)
396                         wrbuf_puts (wr, " Ind: ");
397                     wrbuf_putc(wr, buf[i]);
398                     break;
399                 case YAZ_MARC_SIMPLEXML:
400                     wrbuf_printf(wr, " Indicator%d=\"", j+1);
401                     marc_cdata(mt, buf+i, 1, wr);
402                     wrbuf_printf(wr, "\"");
403                     break;
404                 case YAZ_MARC_OAIMARC:
405                     wrbuf_printf(wr, " i%d=\"", j+1);
406                     marc_cdata(mt, buf+i, 1, wr);
407                     wrbuf_printf(wr, "\"");
408                     break;
409                 case YAZ_MARC_MARCXML:
410                 case YAZ_MARC_XCHANGE:
411                     wrbuf_printf(wr, " ind%d=\"", j+1);
412                     marc_cdata(mt, buf+i, 1, wr);
413                     wrbuf_printf(wr, "\"");
414                 }
415             }
416         }
417         if (mt->xml == YAZ_MARC_SIMPLEXML || mt->xml == YAZ_MARC_MARCXML
418             || mt->xml == YAZ_MARC_OAIMARC || mt->xml == YAZ_MARC_XCHANGE)
419         {
420             wrbuf_puts (wr, ">");
421             if (identifier_flag)
422                 wrbuf_puts (wr, "\n");
423         }
424         if (mt->xml == YAZ_MARC_LINE)
425         {
426             if (mt->debug)
427                 wrbuf_puts (wr, " Fields: ");
428         }
429         if (identifier_flag)
430         {
431             while (buf[i] != ISO2709_RS && buf[i] != ISO2709_FS && i < end_offset)
432             {
433                 int i0;
434                 i++;
435                 switch(mt->xml)
436                 {
437                 case YAZ_MARC_ISO2709:
438                     --i;
439                     wrbuf_iconv_write(wr, mt->iconv_cd, 
440                                       buf+i, identifier_length);
441                     i += identifier_length;
442                     break;
443                 case YAZ_MARC_LINE: 
444                     wrbuf_puts (wr, mt->subfield_str); 
445                     marc_cdata(mt, buf+i, identifier_length-1, wr);
446                     i = i+identifier_length-1;
447                     wrbuf_putc (wr, ' ');
448                     break;
449                 case YAZ_MARC_SIMPLEXML:
450                     wrbuf_puts (wr, "  <subfield code=\"");
451                     marc_cdata(mt, buf+i, identifier_length-1, wr);
452                     i = i+identifier_length-1;
453                     wrbuf_puts (wr, "\">");
454                     break;
455                 case YAZ_MARC_OAIMARC:
456                     wrbuf_puts (wr, "    <subfield label=\"");
457                     marc_cdata(mt, buf+i, identifier_length-1, wr);
458                     i = i+identifier_length-1;
459                     wrbuf_puts (wr, "\">");
460                     break;
461                 case YAZ_MARC_MARCXML:
462                 case YAZ_MARC_XCHANGE:
463                     wrbuf_puts (wr, "    <subfield code=\"");
464                     marc_cdata(mt, buf+i, identifier_length-1, wr);
465                     i = i+identifier_length-1;
466                     wrbuf_puts (wr, "\">");
467                     break;
468                 }
469                 i0 = i;
470                 while (buf[i] != ISO2709_RS && buf[i] != ISO2709_IDFS &&
471                        buf[i] != ISO2709_FS && i < end_offset)
472                     i++;
473                 marc_cdata(mt, buf + i0, i - i0, wr);
474
475                 if (mt->xml == YAZ_MARC_ISO2709 && buf[i] != ISO2709_IDFS)
476                     marc_cdata(mt, buf + i, 1, wr);
477
478                 if (mt->xml == YAZ_MARC_SIMPLEXML || 
479                     mt->xml == YAZ_MARC_MARCXML ||
480                     mt->xml == YAZ_MARC_XCHANGE ||
481                     mt->xml == YAZ_MARC_OAIMARC)
482                     wrbuf_puts (wr, "</subfield>\n");
483             }
484         }
485         else
486         {
487             int i0 = i;
488             while (buf[i] != ISO2709_RS && buf[i] != ISO2709_FS && i < end_offset)
489                 i++;
490             marc_cdata(mt, buf + i0, i - i0, wr);
491             if (mt->xml == YAZ_MARC_ISO2709)
492                 marc_cdata(mt, buf + i, 1, wr);
493         }
494         if (mt->xml == YAZ_MARC_LINE)
495             wrbuf_puts (wr, mt->endline_str);
496         if (i < end_offset)
497             wrbuf_printf(wr, "  <!-- separator but not at end of field length=%d-->\n", data_length);
498         if (buf[i] != ISO2709_RS && buf[i] != ISO2709_FS)
499             wrbuf_printf(wr, "  <!-- no separator at end of field length=%d-->\n", data_length);
500         switch(mt->xml)
501         {
502         case YAZ_MARC_SIMPLEXML:
503             wrbuf_puts (wr, "</field>\n");
504             break;
505         case YAZ_MARC_OAIMARC:
506             if (identifier_flag)
507                 wrbuf_puts (wr, "</varfield>\n");
508             else
509                 wrbuf_puts (wr, "</fixfield>\n");
510             break;
511         case YAZ_MARC_MARCXML:
512         case YAZ_MARC_XCHANGE:
513             if (identifier_flag)
514                 wrbuf_puts (wr, "  </datafield>\n");
515             else
516                 wrbuf_puts (wr, "</controlfield>\n");
517             break;
518         }
519     }
520     switch (mt->xml)
521     {
522     case YAZ_MARC_LINE:
523         wrbuf_puts (wr, "");
524         break;
525     case YAZ_MARC_SIMPLEXML:
526         wrbuf_puts (wr, "</iso2709>\n");
527         break;
528     case YAZ_MARC_OAIMARC:
529         wrbuf_puts (wr, "</oai_marc>\n");
530         break;
531     case YAZ_MARC_MARCXML:
532     case YAZ_MARC_XCHANGE:
533         wrbuf_puts (wr, "</record>\n");
534         break;
535     case YAZ_MARC_ISO2709:
536         wrbuf_putc (wr, ISO2709_RS);
537         break;
538     }
539     return record_length;
540 }
541
542 int yaz_marc_decode_buf (yaz_marc_t mt, const char *buf, int bsize,
543                          char **result, int *rsize)
544 {
545     int r = yaz_marc_decode_wrbuf(mt, buf, bsize, mt->m_wr);
546     if (r > 0)
547     {
548         if (result)
549             *result = wrbuf_buf(mt->m_wr);
550         if (rsize)
551             *rsize = wrbuf_len(mt->m_wr);
552     }
553     return r;
554 }
555
556 void yaz_marc_xml(yaz_marc_t mt, int xmlmode)
557 {
558     if (mt)
559         mt->xml = xmlmode;
560 }
561
562 void yaz_marc_debug(yaz_marc_t mt, int level)
563 {
564     if (mt)
565         mt->debug = level;
566 }
567
568 void yaz_marc_iconv(yaz_marc_t mt, yaz_iconv_t cd)
569 {
570     mt->iconv_cd = cd;
571 }
572
573 /* depricated */
574 int yaz_marc_decode(const char *buf, WRBUF wr, int debug, int bsize, int xml)
575 {
576     yaz_marc_t mt = yaz_marc_create();
577     int r;
578
579     mt->debug = debug;
580     mt->xml = xml;
581     r = yaz_marc_decode_wrbuf(mt, buf, bsize, wr);
582     yaz_marc_destroy(mt);
583     return r;
584 }
585
586 /* depricated */
587 int marc_display_wrbuf (const char *buf, WRBUF wr, int debug, int bsize)
588 {
589     return yaz_marc_decode(buf, wr, debug, bsize, 0);
590 }
591
592 /* depricated */
593 int marc_display_exl (const char *buf, FILE *outf, int debug, int bsize)
594 {
595     yaz_marc_t mt = yaz_marc_create();
596     int r;
597
598     mt->debug = debug;
599     r = yaz_marc_decode_wrbuf (mt, buf, bsize, mt->m_wr);
600     if (!outf)
601         outf = stdout;
602     if (r > 0)
603         fwrite (wrbuf_buf(mt->m_wr), 1, wrbuf_len(mt->m_wr), outf);
604     yaz_marc_destroy(mt);
605     return r;
606 }
607
608 /* depricated */
609 int marc_display_ex (const char *buf, FILE *outf, int debug)
610 {
611     return marc_display_exl (buf, outf, debug, -1);
612 }
613
614 /* depricated */
615 int marc_display (const char *buf, FILE *outf)
616 {
617     return marc_display_ex (buf, outf, 0);
618 }
619