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