TCPD libs only used in libyaz's LIBADD
[yaz-moved-to-github.git] / src / marc_read_iso2709.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2010 Index Data
3  * See the file LICENSE for details.
4  */
5
6 /**
7  * \file marc_read_iso2709.c
8  * \brief Implements reading of MARC as ISO2709
9  */
10
11 #if HAVE_CONFIG_H
12 #include <config.h>
13 #endif
14
15 #ifdef WIN32
16 #include <windows.h>
17 #endif
18
19 #include <stdio.h>
20 #include <string.h>
21 #include <ctype.h>
22 #include <yaz/marcdisp.h>
23 #include <yaz/wrbuf.h>
24 #include <yaz/yaz-util.h>
25
26 int yaz_marc_read_iso2709(yaz_marc_t mt, const char *buf, int bsize)
27 {
28     int entry_p;
29     int record_length;
30     int indicator_length;
31     int identifier_length;
32     int end_of_directory;
33     int base_address;
34     int length_data_entry;
35     int length_starting;
36     int length_implementation;
37
38     yaz_marc_reset(mt);
39
40     record_length = atoi_n (buf, 5);
41     if (record_length < 25)
42     {
43         yaz_marc_cprintf(mt, "Record length %d < 24", record_length);
44         return -1;
45     }
46     /* ballout if bsize is known and record_length is less than that */
47     if (bsize != -1 && record_length > bsize)
48     {
49         yaz_marc_cprintf(mt, "Record appears to be larger than buffer %d < %d",
50                          record_length, bsize);
51         return -1;
52     }
53     if (yaz_marc_get_debug(mt))
54         yaz_marc_cprintf(mt, "Record length         %5d", record_length);
55
56     yaz_marc_set_leader(mt, buf,
57                         &indicator_length,
58                         &identifier_length,
59                         &base_address,
60                         &length_data_entry,
61                         &length_starting,
62                         &length_implementation);
63
64     /* First pass. determine length of directory & base of data */
65     for (entry_p = 24; buf[entry_p] != ISO2709_FS; )
66     {
67         /* length of directory entry */
68         int l = 3 + length_data_entry + length_starting;
69         if (entry_p + l >= record_length)
70         {
71             yaz_marc_cprintf(mt, "Directory offset %d: end of record."
72                              " Missing FS char", entry_p);
73             return -1;
74         }
75         if (yaz_marc_get_debug(mt))
76         {
77             yaz_marc_cprintf(mt, "Directory offset %d: Tag %.3s",
78                              entry_p, buf+entry_p);
79         }
80         /* Check for digits in length info */
81         while (--l >= 3)
82             if (!isdigit(*(const unsigned char *) (buf + entry_p+l)))
83                 break;
84         if (l >= 3)
85         {
86             /* Not all digits, so stop directory scan */
87             yaz_marc_cprintf(mt, "Directory offset %d: Bad value for data"
88                              " length and/or length starting", entry_p);
89             break;
90         }
91         entry_p += 3 + length_data_entry + length_starting;
92     }
93     end_of_directory = entry_p;
94     if (base_address != entry_p+1)
95     {
96         yaz_marc_cprintf(mt, "Base address not at end of directory,"
97                          " base %d, end %d", base_address, entry_p+1);
98     }
99
100     /* Second pass. parse control - and datafields */
101     for (entry_p = 24; entry_p != end_of_directory; )
102     {
103         int data_length;
104         int data_offset;
105         int end_offset;
106         int i;
107         char tag[4];
108         int identifier_flag = 0;
109         int entry_p0 = entry_p;
110
111         memcpy (tag, buf+entry_p, 3);
112         entry_p += 3;
113         tag[3] = '\0';
114         data_length = atoi_n(buf+entry_p, length_data_entry);
115         entry_p += length_data_entry;
116         data_offset = atoi_n(buf+entry_p, length_starting);
117         entry_p += length_starting;
118         i = data_offset + base_address;
119         end_offset = i+data_length-1;
120
121         if (data_length <= 0 || data_offset < 0)
122             break;
123         
124         if (yaz_marc_get_debug(mt))
125         {
126             yaz_marc_cprintf(mt, "Tag: %s. Directory offset %d: data-length %d,"
127                              " data-offset %d",
128                              tag, entry_p0, data_length, data_offset);
129         }
130         if (end_offset >= record_length)
131         {
132             yaz_marc_cprintf(mt, "Directory offset %d: Data out of bounds %d >= %d",
133                              entry_p0, end_offset, record_length);
134             break;
135         }
136         
137         if (memcmp (tag, "00", 2))
138             identifier_flag = 1;  /* if not 00X assume subfields */
139         else if (indicator_length < 4 && indicator_length > 0)
140         {
141             /* Danmarc 00X have subfields */
142             if (buf[i + indicator_length] == ISO2709_IDFS)
143                 identifier_flag = 1;
144             else if (buf[i + indicator_length + 1] == ISO2709_IDFS)
145                 identifier_flag = 2;
146         }
147
148         if (identifier_flag)
149         {
150             /* datafield */
151             i += identifier_flag-1;
152             if (indicator_length)
153             {
154                 /* skip RS/FS bytes in indicator. They are not allowed there */
155                 int j;
156                 for (j = indicator_length; --j >= 0; )
157                     if (buf[j+i] < ' ')
158                     {
159                         j++;
160                         i += j;
161                         end_offset += j;
162                         yaz_marc_cprintf(mt, "Bad indicator data. "
163                                          "Skipping %d bytes", j);
164                         break;
165                     }
166                 yaz_marc_add_datafield(mt, tag, buf+i, indicator_length);
167                 i += indicator_length;
168             }
169
170             while (i < end_offset &&
171                     buf[i] != ISO2709_RS && buf[i] != ISO2709_FS)
172             {
173                 int code_offset = i+1;
174
175                 i ++;
176                 while (i < end_offset &&
177                         buf[i] != ISO2709_RS && buf[i] != ISO2709_IDFS &&
178                        buf[i] != ISO2709_FS)
179                     i++;
180                 if (i > code_offset)
181                     yaz_marc_add_subfield(mt, buf+code_offset, i - code_offset);
182             }
183         }
184         else
185         {
186             /* controlfield */
187             int i0 = i;
188             while (i < end_offset && 
189                 buf[i] != ISO2709_RS && buf[i] != ISO2709_FS)
190                 i++;
191             yaz_marc_add_controlfield(mt, tag, buf+i0, i-i0);
192         }
193         if (i < end_offset)
194         {
195             yaz_marc_cprintf(mt, "Separator but not at end of field length=%d",
196                     data_length);
197         }
198         if (buf[i] != ISO2709_RS && buf[i] != ISO2709_FS)
199         {
200             yaz_marc_cprintf(mt, "No separator at end of field length=%d",
201                              data_length);
202         }
203     }
204     return record_length;
205 }
206
207 /*
208  * Local variables:
209  * c-basic-offset: 4
210  * c-file-style: "Stroustrup"
211  * indent-tabs-mode: nil
212  * End:
213  * vim: shiftwidth=4 tabstop=8 expandtab
214  */
215