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