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