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