Original 2.4
[marc4j.git] / src / org / marc4j / MarcStreamReader.java
1 // $Id: MarcStreamReader.java,v 1.11 2008/09/26 21:17:42 haschart Exp $\r
2 /**\r
3  * Copyright (C) 2004 Bas Peters\r
4  *\r
5  * This file is part of MARC4J\r
6  *\r
7  * MARC4J is free software; you can redistribute it and/or\r
8  * modify it under the terms of the GNU Lesser General Public \r
9  * License as published by the Free Software Foundation; either \r
10  * version 2.1 of the License, or (at your option) any later version.\r
11  *\r
12  * MARC4J is distributed in the hope that it will be useful,\r
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
15  * Lesser General Public License for more details.\r
16  *\r
17  * You should have received a copy of the GNU Lesser General Public \r
18  * License along with MARC4J; if not, write to the Free Software\r
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
20  */\r
21 package org.marc4j;\r
22 \r
23 import java.io.BufferedInputStream;\r
24 import java.io.ByteArrayInputStream;\r
25 import java.io.DataInputStream;\r
26 import java.io.EOFException;\r
27 import java.io.IOException;\r
28 import java.io.InputStream;\r
29 import java.io.InputStreamReader;\r
30 import java.io.UnsupportedEncodingException;\r
31 import java.util.HashMap;\r
32 import java.util.Iterator;\r
33 import java.util.List;\r
34 \r
35 import org.marc4j.converter.CharConverter;\r
36 import org.marc4j.converter.impl.AnselToUnicode;\r
37 import org.marc4j.marc.ControlField;\r
38 import org.marc4j.marc.DataField;\r
39 import org.marc4j.marc.Leader;\r
40 import org.marc4j.marc.MarcFactory;\r
41 import org.marc4j.marc.Record;\r
42 import org.marc4j.marc.Subfield;\r
43 import org.marc4j.marc.VariableField;\r
44 import org.marc4j.marc.impl.Verifier;\r
45 \r
46 /**\r
47  * An iterator over a collection of MARC records in ISO 2709 format.\r
48  * <p>\r
49  * Example usage:\r
50  * \r
51  * <pre>\r
52  * InputStream input = new FileInputStream(&quot;file.mrc&quot;);\r
53  * MarcReader reader = new MarcStreamReader(input);\r
54  * while (reader.hasNext()) {\r
55  *     Record record = reader.next();\r
56  *     // Process record\r
57  * }\r
58  * </pre>\r
59  * \r
60  * <p>\r
61  * Check the {@link org.marc4j.marc}&nbsp;package for examples about the use of\r
62  * the {@link org.marc4j.marc.Record}&nbsp;object model.\r
63  * </p>\r
64  * \r
65  * <p>\r
66  * When no encoding is given as an constructor argument the parser tries to\r
67  * resolve the encoding by looking at the character coding scheme (leader\r
68  * position 9) in MARC21 records. For UNIMARC records this position is not\r
69  * defined.\r
70  * </p>\r
71  * \r
72  * @author Bas Peters\r
73  * @version $Revision: 1.11 $\r
74  * \r
75  */\r
76 public class MarcStreamReader implements MarcReader {\r
77 \r
78     private DataInputStream input = null;\r
79 \r
80     private Record record;\r
81 \r
82     private MarcFactory factory;\r
83 \r
84     private String encoding = "ISO8859_1";\r
85 \r
86     private boolean override = false;\r
87        \r
88     private CharConverter converterAnsel = null;\r
89 \r
90     /**\r
91      * Constructs an instance with the specified input stream.\r
92      */\r
93     public MarcStreamReader(InputStream input) {\r
94         this(input, null);\r
95     }\r
96 \r
97     /**\r
98      * Constructs an instance with the specified input stream.\r
99      */\r
100     public MarcStreamReader(InputStream input, String encoding) {\r
101         this.input = new DataInputStream(new BufferedInputStream(input));\r
102         factory = MarcFactory.newInstance();\r
103         if (encoding != null) {\r
104             this.encoding = encoding;\r
105             override = true;\r
106         }\r
107     }\r
108 \r
109     /**\r
110      * Returns true if the iteration has more records, false otherwise.\r
111      */\r
112     public boolean hasNext() {\r
113         try {\r
114             if (input.available() == 0)\r
115                 return false;\r
116         } catch (IOException e) {\r
117             throw new MarcException(e.getMessage(), e);\r
118         }\r
119         return true;\r
120     }\r
121 \r
122     /**\r
123      * Returns the next record in the iteration.\r
124      * \r
125      * @return Record - the record object\r
126      */\r
127     public Record next() \r
128     {\r
129         record = factory.newRecord();\r
130 \r
131         try {\r
132 \r
133             byte[] byteArray = new byte[24];\r
134             input.readFully(byteArray);\r
135 \r
136             int recordLength = parseRecordLength(byteArray);\r
137             byte[] recordBuf = new byte[recordLength - 24];\r
138             input.readFully(recordBuf);\r
139             parseRecord(record, byteArray, recordBuf, recordLength);\r
140             return(record);\r
141         }\r
142         catch (EOFException e) {\r
143             throw new MarcException("Premature end of file encountered", e);\r
144         } \r
145         catch (IOException e) {\r
146             throw new MarcException("an error occured reading input", e);\r
147         }   \r
148     }\r
149     \r
150     private void parseRecord(Record record, byte[] byteArray, byte[] recordBuf, int recordLength)\r
151     {\r
152         Leader ldr;\r
153         ldr = factory.newLeader();\r
154         ldr.setRecordLength(recordLength);\r
155         int directoryLength=0;\r
156         \r
157         try {                \r
158             parseLeader(ldr, byteArray);\r
159             directoryLength = ldr.getBaseAddressOfData() - (24 + 1);\r
160         } \r
161         catch (IOException e) {\r
162             throw new MarcException("error parsing leader with data: "\r
163                     + new String(byteArray), e);\r
164         } \r
165         catch (MarcException e) {\r
166             throw new MarcException("error parsing leader with data: "\r
167                     + new String(byteArray), e);\r
168         }\r
169 \r
170         // if MARC 21 then check encoding\r
171         switch (ldr.getCharCodingScheme()) {\r
172         case ' ':\r
173             if (!override)\r
174                 encoding = "ISO-8859-1";\r
175             break;\r
176         case 'a':\r
177             if (!override)\r
178                 encoding = "UTF8";\r
179         }\r
180         record.setLeader(ldr);\r
181         \r
182         if ((directoryLength % 12) != 0)\r
183         {\r
184             throw new MarcException("invalid directory");\r
185         }\r
186         DataInputStream inputrec = new DataInputStream(new ByteArrayInputStream(recordBuf));\r
187         int size = directoryLength / 12;\r
188 \r
189         String[] tags = new String[size];\r
190         int[] lengths = new int[size];\r
191 \r
192         byte[] tag = new byte[3];\r
193         byte[] length = new byte[4];\r
194         byte[] start = new byte[5];\r
195 \r
196         String tmp;\r
197 \r
198         try {\r
199             for (int i = 0; i < size; i++) \r
200             {\r
201                 inputrec.readFully(tag);                \r
202                 tmp = new String(tag);\r
203                 tags[i] = tmp;\r
204     \r
205                 inputrec.readFully(length);\r
206                 tmp = new String(length);\r
207                 lengths[i] = Integer.parseInt(tmp);\r
208     \r
209                 inputrec.readFully(start);\r
210             }\r
211     \r
212             if (inputrec.read() != Constants.FT)\r
213             {\r
214                 throw new MarcException("expected field terminator at end of directory");\r
215             }\r
216             \r
217             for (int i = 0; i < size; i++) \r
218             {\r
219                 int fieldLength = getFieldLength(inputrec);\r
220                 if (Verifier.isControlField(tags[i])) \r
221                 {\r
222                     byteArray = new byte[lengths[i] - 1];\r
223                     inputrec.readFully(byteArray);\r
224     \r
225                     if (inputrec.read() != Constants.FT)\r
226                     {\r
227                         throw new MarcException("expected field terminator at end of field");\r
228                     }\r
229     \r
230                     ControlField field = factory.newControlField();\r
231                     field.setTag(tags[i]);\r
232                     field.setData(getDataAsString(byteArray));\r
233                     record.addVariableField(field);\r
234                 } \r
235                 else \r
236                 {\r
237                     byteArray = new byte[lengths[i]];\r
238                     inputrec.readFully(byteArray);\r
239     \r
240                     try {\r
241                         record.addVariableField(parseDataField(tags[i], byteArray));\r
242                     } catch (IOException e) {\r
243                         throw new MarcException(\r
244                                 "error parsing data field for tag: " + tags[i]\r
245                                         + " with data: "\r
246                                         + new String(byteArray), e);\r
247                     }\r
248                 }\r
249             }\r
250             \r
251             if (inputrec.read() != Constants.RT)\r
252             {\r
253                 throw new MarcException("expected record terminator");\r
254             } \r
255         }\r
256         catch (IOException e)\r
257         {\r
258             throw new MarcException("an error occured reading input", e);            \r
259         }\r
260     }\r
261 \r
262     private DataField parseDataField(String tag, byte[] field)\r
263             throws IOException {\r
264         ByteArrayInputStream bais = new ByteArrayInputStream(field);\r
265         char ind1 = (char) bais.read();\r
266         char ind2 = (char) bais.read();\r
267 \r
268         DataField dataField = factory.newDataField();\r
269         dataField.setTag(tag);\r
270         dataField.setIndicator1(ind1);\r
271         dataField.setIndicator2(ind2);\r
272 \r
273         int code;\r
274         int size;\r
275         int readByte;\r
276         byte[] data;\r
277         Subfield subfield;\r
278         while (true) {\r
279             readByte = bais.read();\r
280             if (readByte < 0)\r
281                 break;\r
282             switch (readByte) {\r
283             case Constants.US:\r
284                 code = bais.read();\r
285                 if (code < 0)\r
286                     throw new IOException("unexpected end of data field");\r
287                 if (code == Constants.FT)\r
288                     break;\r
289                 size = getSubfieldLength(bais);\r
290                 data = new byte[size];\r
291                 bais.read(data);\r
292                 subfield = factory.newSubfield();\r
293                 subfield.setCode((char) code);\r
294                 subfield.setData(getDataAsString(data));\r
295                 dataField.addSubfield(subfield);\r
296                 break;\r
297             case Constants.FT:\r
298                 break;\r
299             }\r
300         }\r
301         return dataField;\r
302     }\r
303     \r
304     private int getFieldLength(DataInputStream bais) throws IOException \r
305     {\r
306         bais.mark(9999);\r
307         int bytesRead = 0;\r
308         while (true) {\r
309             switch (bais.read()) {\r
310              case Constants.FT:\r
311                 bais.reset();\r
312                 return bytesRead;\r
313             case -1:\r
314                 bais.reset();\r
315                 throw new IOException("Field not terminated");\r
316             case Constants.US:\r
317             default:\r
318                 bytesRead++;\r
319             }\r
320         }\r
321     }\r
322 \r
323     private int getSubfieldLength(ByteArrayInputStream bais) throws IOException {\r
324         bais.mark(9999);\r
325         int bytesRead = 0;\r
326         while (true) {\r
327             switch (bais.read()) {\r
328             case Constants.US:\r
329             case Constants.FT:\r
330                 bais.reset();\r
331                 return bytesRead;\r
332             case -1:\r
333                 bais.reset();\r
334                 throw new IOException("subfield not terminated");\r
335             default:\r
336                 bytesRead++;\r
337             }\r
338         }\r
339     }\r
340 \r
341     private int parseRecordLength(byte[] leaderData) throws IOException {\r
342         InputStreamReader isr = new InputStreamReader(new ByteArrayInputStream(\r
343                 leaderData));\r
344         int length = -1;\r
345         char[] tmp = new char[5];\r
346         isr.read(tmp);\r
347         try {\r
348             length = Integer.parseInt(new String(tmp));\r
349         } catch (NumberFormatException e) {\r
350             throw new MarcException("unable to parse record length", e);\r
351         }\r
352         return(length);\r
353     }\r
354     \r
355     private void parseLeader(Leader ldr, byte[] leaderData) throws IOException {\r
356         InputStreamReader isr = new InputStreamReader(new ByteArrayInputStream(\r
357                 leaderData));\r
358         char[] tmp = new char[5];\r
359         isr.read(tmp);\r
360         //  Skip over bytes for record length, If we get here, its already been computed.\r
361         ldr.setRecordStatus((char) isr.read());\r
362         ldr.setTypeOfRecord((char) isr.read());\r
363         tmp = new char[2];\r
364         isr.read(tmp);\r
365         ldr.setImplDefined1(tmp);\r
366         ldr.setCharCodingScheme((char) isr.read());\r
367         char indicatorCount = (char) isr.read();\r
368         char subfieldCodeLength = (char) isr.read();\r
369         char baseAddr[] = new char[5];\r
370         isr.read(baseAddr);\r
371         tmp = new char[3];\r
372         isr.read(tmp);\r
373         ldr.setImplDefined2(tmp);\r
374         tmp = new char[4];\r
375         isr.read(tmp);\r
376         ldr.setEntryMap(tmp);\r
377         isr.close();\r
378         try {\r
379             ldr.setIndicatorCount(Integer.parseInt(String.valueOf(indicatorCount)));\r
380         } catch (NumberFormatException e) {\r
381             throw new MarcException("unable to parse indicator count", e);\r
382         }\r
383         try {\r
384             ldr.setSubfieldCodeLength(Integer.parseInt(String\r
385                     .valueOf(subfieldCodeLength)));\r
386         } catch (NumberFormatException e) {\r
387             throw new MarcException("unable to parse subfield code length", e);\r
388         }\r
389         try {\r
390             ldr.setBaseAddressOfData(Integer.parseInt(new String(baseAddr)));\r
391         } catch (NumberFormatException e) {\r
392             throw new MarcException("unable to parse base address of data", e);\r
393         }\r
394 \r
395     }\r
396 \r
397     private String getDataAsString(byte[] bytes) \r
398     {\r
399         String dataElement = null;\r
400         if (encoding.equals("UTF-8") || encoding.equals("UTF8"))\r
401         {\r
402             try {\r
403                 dataElement = new String(bytes, "UTF8");\r
404             } \r
405             catch (UnsupportedEncodingException e) {\r
406                 throw new MarcException("unsupported encoding", e);\r
407             }\r
408         }\r
409         else if (encoding.equals("MARC-8") || encoding.equals("MARC8"))\r
410         {\r
411             if (converterAnsel == null) converterAnsel = new AnselToUnicode();\r
412             dataElement = converterAnsel.convert(bytes);\r
413         }\r
414         else if (encoding.equals("ISO-8859-1") || encoding.equals("ISO8859_1"))\r
415         {\r
416             try {\r
417                 dataElement = new String(bytes, "ISO-8859-1");\r
418             } \r
419             catch (UnsupportedEncodingException e) {\r
420                 throw new MarcException("unsupported encoding", e);\r
421             }\r
422         }\r
423         return dataElement;\r
424     }\r
425     \r
426 }