Make List type safe. Remove/suppress all warnings.
[marc4j.git] / src / org / marc4j / converter / impl / AnselToUnicode.java
1 // $Id: AnselToUnicode.java,v 1.5 2008/10/17 06:47:06 haschart Exp $\r
2 /**\r
3  * Copyright (C) 2002 Bas Peters (mail@bpeters.com)\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.converter.impl;\r
22 \r
23 import java.io.InputStream;\r
24 import java.lang.reflect.Constructor;\r
25 import java.util.Vector;\r
26 \r
27 import org.marc4j.ErrorHandler;\r
28 import org.marc4j.MarcException;\r
29 import org.marc4j.converter.CharConverter;\r
30 \r
31 /**\r
32  * <p>\r
33  * A utility to convert MARC-8 data to non-precomposed UCS/Unicode.\r
34  * </p>\r
35  * \r
36  * <p>\r
37  * The MARC-8 to Unicode mapping used is the version with the March 2005\r
38  * revisions.\r
39  * </p>\r
40  * \r
41  * @author Bas Peters\r
42  * @author Corey Keith\r
43  * @version $Revision: 1.5 $\r
44  */\r
45 public class AnselToUnicode extends CharConverter {\r
46 \r
47     @SuppressWarnings("rawtypes")\r
48     class Queue extends Vector {\r
49 \r
50         /**\r
51        * \r
52        */\r
53       private static final long serialVersionUID = 7414878465947143461L;\r
54 \r
55         /**\r
56          * Puts an item into the queue.\r
57          * \r
58          * @param item\r
59          *            the item to be put into the queue.\r
60          */\r
61         @SuppressWarnings("unchecked")\r
62         public Object put(Object item) {\r
63             addElement(item);\r
64 \r
65             return item;\r
66         }\r
67 \r
68         /**\r
69          * Gets an item from the front of the queue.\r
70          */\r
71         public Object get() {\r
72             Object obj;\r
73             @SuppressWarnings("unused")\r
74             int len = size();\r
75 \r
76             obj = peek();\r
77             removeElementAt(0);\r
78 \r
79             return obj;\r
80         }\r
81 \r
82         /**\r
83          * Peeks at the front of the queue.\r
84          */\r
85         public Object peek() {\r
86             @SuppressWarnings("unused")\r
87             int len = size();\r
88 \r
89             return elementAt(0);\r
90         }\r
91 \r
92         /**\r
93          * Returns true if the queue is empty.\r
94          */\r
95         public boolean empty() {\r
96             return size() == 0;\r
97         }\r
98     }\r
99 \r
100     class CodeTracker {\r
101         int offset;\r
102 \r
103         int g0;\r
104 \r
105         int g1;\r
106 \r
107         boolean multibyte;\r
108 \r
109         public String toString() {\r
110             return "Offset: " + offset + " G0: " + Integer.toHexString(g0)\r
111                     + " G1: " + Integer.toHexString(g1) + " Multibyte: "\r
112                     + multibyte;\r
113         }\r
114     }\r
115 \r
116     protected CodeTableInterface ct;\r
117 \r
118     protected boolean loadedMultibyte = false;\r
119 \r
120     protected ErrorHandler errorList = null;\r
121     /**\r
122      * Creates a new instance and loads the MARC4J supplied\r
123      * conversion tables based on the official LC tables.\r
124      *  \r
125      */\r
126     public AnselToUnicode() \r
127     {\r
128         ct = loadGeneratedTable(false);\r
129     }\r
130     \r
131     /**\r
132      * Creates a new instance and loads the MARC4J supplied\r
133      * conversion tables based on the official LC tables.\r
134      *  \r
135      */\r
136     public AnselToUnicode(boolean loadMultibyte) \r
137     {\r
138         ct = loadGeneratedTable(loadMultibyte);\r
139     }\r
140     /**\r
141      * Creates a new instance and loads the MARC4J supplied\r
142      * conversion tables based on the official LC tables.\r
143      *  \r
144      */\r
145     public AnselToUnicode(ErrorHandler errorList) \r
146     {\r
147         ct = loadGeneratedTable(false);\r
148         this.errorList = errorList;\r
149     }\r
150 \r
151     /**\r
152      * Creates a new instance and loads the MARC4J supplied\r
153      * conversion tables based on the official LC tables.\r
154      *  \r
155      */\r
156     public AnselToUnicode(ErrorHandler errorList, boolean loadMultibyte) \r
157     {\r
158         ct = loadGeneratedTable(loadMultibyte);\r
159         this.errorList = errorList;\r
160     }\r
161 \r
162 \r
163     @SuppressWarnings({ "rawtypes", "unchecked" })\r
164     private CodeTableInterface loadGeneratedTable(boolean loadMultibyte) \r
165     {\r
166         try\r
167         {\r
168             Class generated = Class.forName("org.marc4j.converter.impl.CodeTableGenerated");\r
169             Constructor cons = generated.getConstructor();\r
170             Object ct = cons.newInstance();\r
171             loadedMultibyte = true;\r
172             return((CodeTableInterface)ct);\r
173         }\r
174         catch (Exception e)\r
175         {\r
176             CodeTableInterface ct;\r
177             if (loadMultibyte)\r
178             {\r
179                 ct = new CodeTable(AnselToUnicode.class.getResourceAsStream("resources/codetables.xml"));                \r
180             }\r
181             else\r
182             {\r
183                 ct = new CodeTable(AnselToUnicode.class.getResourceAsStream("resources/codetablesnocjk.xml"));\r
184             }\r
185             loadedMultibyte = loadMultibyte;\r
186             return(ct);\r
187          }\r
188 \r
189     }\r
190     \r
191     /**\r
192      * Constructs an instance with the specified pathname.\r
193      * \r
194      * Use this constructor to create an instance with a customized code table\r
195      * mapping. The mapping file should follow the structure of LC's XML MARC-8\r
196      * to Unicode mapping (see:\r
197      * http://www.loc.gov/marc/specifications/codetables.xml).\r
198      *  \r
199      */\r
200     public AnselToUnicode(String pathname) {\r
201         ct = new CodeTable(pathname);\r
202         loadedMultibyte = true;\r
203     }\r
204 \r
205     /**\r
206      * Constructs an instance with the specified input stream.\r
207      * \r
208      * Use this constructor to create an instance with a customized code table\r
209      * mapping. The mapping file should follow the structure of LC's XML MARC-8\r
210      * to Unicode mapping (see:\r
211      * http://www.loc.gov/marc/specifications/codetables.xml).\r
212      *  \r
213      */\r
214     public AnselToUnicode(InputStream in) {\r
215         ct = new CodeTable(in);\r
216         loadedMultibyte = true;\r
217     }\r
218 \r
219     /**\r
220      * Loads the entire mapping (including multibyte characters) from the Library\r
221      * of Congress.\r
222      */\r
223     private void loadMultibyte() {\r
224         ct = new CodeTable(getClass().getResourceAsStream(\r
225                 "resources/codetables.xml"));\r
226     }\r
227 \r
228     private void checkMode(char[] data, CodeTracker cdt) {\r
229         int extra = 0;\r
230         int extra2 = 0;\r
231         @SuppressWarnings("unused")\r
232         int extra3 = 0;\r
233         while (cdt.offset + extra + extra2< data.length && isEscape(data[cdt.offset])) {\r
234             switch (data[cdt.offset + 1 + extra]) {\r
235             case 0x28:  // '('\r
236             case 0x2c:  // ','\r
237                 set_cdt(cdt, 0, data, 2 + extra, false); \r
238                 break;\r
239             case 0x29:  // ')'\r
240             case 0x2d:  // '-'\r
241                 set_cdt(cdt, 1, data, 2 + extra, false); \r
242                 break;\r
243             case 0x24:  // '$'\r
244                 if (!loadedMultibyte) {\r
245                     loadMultibyte();\r
246                     loadedMultibyte = true;\r
247                 }\r
248                 switch (data[cdt.offset + 2 + extra + extra2]) {\r
249                 case 0x29:  // ')'\r
250                 case 0x2d:  // '-'\r
251                     set_cdt(cdt, 1, data, 3 + extra + extra2, true); \r
252                     break;\r
253                 case 0x2c:  // ','\r
254                     set_cdt(cdt, 0, data, 3 + extra + extra2, true); \r
255                     break;\r
256                 case 0x31:  // '1'\r
257                     cdt.g0 = data[cdt.offset + 2 + extra + extra2];\r
258                     cdt.offset += 3 + extra + extra2;\r
259                     cdt.multibyte = true;\r
260                     break;\r
261                 case 0x20:  // ' ' \r
262                     // space found in escape code: look ahead and try to proceed\r
263                     extra2++;\r
264                     break;\r
265                 default: \r
266                     // unknown code character found: discard escape sequence and return\r
267                     cdt.offset += 1;\r
268                     if (errorList != null)\r
269                     {\r
270                         errorList.addError(ErrorHandler.MINOR_ERROR, "Unknown character set code found following escape character. Discarding escape character.");\r
271                     }\r
272                     else\r
273                     {\r
274                         throw new MarcException("Unknown character set code found following escape character.");\r
275                     }\r
276                     break;\r
277                 }\r
278                 break;\r
279             case 0x67:  // 'g'\r
280             case 0x62:  // 'b'\r
281             case 0x70:  // 'p'\r
282                 cdt.g0 = data[cdt.offset + 1 + extra];\r
283                 cdt.offset += 2 + extra;\r
284                 cdt.multibyte = false;\r
285                 break;\r
286             case 0x73:  // 's'\r
287                 cdt.g0 = 0x42;\r
288                 cdt.offset += 2 + extra;\r
289                 cdt.multibyte = false;\r
290                 break;\r
291             case 0x20:  // ' ' \r
292                 // space found in escape code: look ahead and try to proceed\r
293                 if (errorList == null)\r
294                 {\r
295                     throw new MarcException("Extraneous space character found within MARC8 character set escape sequence");\r
296                 }\r
297                 extra++;\r
298                 break;\r
299             default: \r
300                 // unknown code character found: discard escape sequence and return\r
301                 cdt.offset += 1;\r
302                 if (errorList != null)\r
303                 {\r
304                     errorList.addError(ErrorHandler.MINOR_ERROR, "Unknown character set code found following escape character. Discarding escape character.");\r
305                 }\r
306                 else\r
307                 {\r
308                     throw new MarcException("Unknown character set code found following escape character.");\r
309                 }\r
310                 break;\r
311             }\r
312         }\r
313         if (errorList != null && ( extra != 0 || extra2 != 0))\r
314         {\r
315             errorList.addError(ErrorHandler.ERROR_TYPO, "" + (extra+extra2) + " extraneous space characters found within MARC8 character set escape sequence");\r
316         }\r
317     }\r
318 \r
319     private void set_cdt(CodeTracker cdt, int g0_or_g1, char[] data, int addnlOffset, boolean multibyte)\r
320     {\r
321         if (data[cdt.offset + addnlOffset] == '!' && data[cdt.offset + addnlOffset + 1] == 'E') \r
322         {\r
323             addnlOffset++;\r
324         }\r
325         else if (data[cdt.offset + addnlOffset] == ' ') \r
326         {\r
327             if (errorList != null)\r
328             {\r
329                 errorList.addError(ErrorHandler.ERROR_TYPO, "Extraneous space character found within MARC8 character set escape sequence. Skipping over space.");\r
330             }           \r
331             else\r
332             {\r
333                 throw new MarcException("Extraneous space character found within MARC8 character set escape sequence");\r
334             }\r
335             addnlOffset++;\r
336         }\r
337         else if ("(,)-$!".indexOf(data[cdt.offset + addnlOffset]) != -1) \r
338         {\r
339             if (errorList != null)\r
340             {\r
341                 errorList.addError(ErrorHandler.MINOR_ERROR, "Extraneaous intermediate character found following escape character. Discarding intermediate character.");\r
342             }           \r
343             else\r
344             {\r
345                 throw new MarcException("Extraneaous intermediate character found following escape character.");\r
346             }\r
347             addnlOffset++;\r
348         }\r
349         if ("34BE1NQS2".indexOf(data[cdt.offset + addnlOffset]) == -1)\r
350         {\r
351             cdt.offset += 1;\r
352             cdt.multibyte = false;\r
353             if (errorList != null)\r
354             {\r
355                 errorList.addError(ErrorHandler.MINOR_ERROR, "Unknown character set code found following escape character. Discarding escape character.");\r
356             }           \r
357             else\r
358             {\r
359                 throw new MarcException("Unknown character set code found following escape character.");\r
360             }\r
361         }\r
362         else  // All is well, proceed normally\r
363         {\r
364             if (g0_or_g1 == 0) cdt.g0 = data[cdt.offset + addnlOffset];\r
365             else               cdt.g1 = data[cdt.offset + addnlOffset];\r
366             cdt.offset += 1 + addnlOffset;\r
367             cdt.multibyte = multibyte;\r
368         }\r
369     }\r
370     /**\r
371      * <p>\r
372      * Converts MARC-8 data to UCS/Unicode.\r
373      * </p>\r
374      * \r
375      * @param data -  the MARC-8 data in an array of char\r
376      * @return String - the UCS/Unicode data\r
377      */\r
378     public String convert(char  data[]) \r
379     {\r
380         StringBuffer sb = new StringBuffer();\r
381         int len = data.length;\r
382 \r
383         CodeTracker cdt = new CodeTracker();\r
384 \r
385         cdt.g0 = 0x42;\r
386         cdt.g1 = 0x45;\r
387         cdt.multibyte = false;\r
388 \r
389         cdt.offset = 0;\r
390 \r
391         checkMode(data, cdt);\r
392 \r
393         Queue diacritics = new Queue();\r
394 \r
395         while (cdt.offset < data.length) \r
396         {\r
397             if (ct.isCombining(data[cdt.offset], cdt.g0, cdt.g1)\r
398                     && hasNext(cdt.offset, len)) \r
399             {\r
400 \r
401                 while (ct.isCombining(data[cdt.offset], cdt.g0, cdt.g1)\r
402                         && hasNext(cdt.offset, len)) \r
403                 {\r
404                     char c = getChar(data[cdt.offset], cdt.g0, cdt.g1);\r
405                     if (c != 0) diacritics.put(new Character(c));\r
406                     cdt.offset++;\r
407                     checkMode(data, cdt);\r
408                 }\r
409 \r
410                 char c2 = getChar(data[cdt.offset], cdt.g0, cdt.g1);\r
411                 cdt.offset++;\r
412                 checkMode(data, cdt);\r
413                 if (c2 != 0) sb.append(c2);\r
414 \r
415                 while (!diacritics.isEmpty()) \r
416                 {\r
417                     char c1 = ((Character) diacritics.get()).charValue();\r
418                     sb.append(c1);\r
419                 }\r
420 \r
421             } \r
422             else if (cdt.multibyte)\r
423             {\r
424                 if (data[cdt.offset]== 0x20)\r
425                 {\r
426                     // if a 0x20 byte occurs amidst a sequence of multibyte characters\r
427                     // skip over it and output a space.\r
428                     // Hmmm.  If the following line is present it seems to output two spaces \r
429                     // when a space occurs in multibytes chars, without it one seems to be output.\r
430                     //    sb.append(getChar(data[cdt.offset], cdt.g0, cdt.g1));\r
431                     cdt.offset += 1;\r
432                 }\r
433                 else if (cdt.offset + 3 <= data.length && (errorList == null || data[cdt.offset+1]!= 0x20 && data[cdt.offset+2]!= 0x20)) \r
434                 {\r
435                     char c = getMBChar(makeMultibyte(data[cdt.offset], data[cdt.offset+1], data[cdt.offset+2]));\r
436                     if (errorList == null  || c != 0)\r
437                     { \r
438                         sb.append(c);\r
439                         cdt.offset += 3;\r
440                     }\r
441                     else if (cdt.offset + 6 <= data.length && data[cdt.offset+4]!= 0x20 && data[cdt.offset+5]!= 0x20 &&\r
442                             getMBChar(makeMultibyte(data[cdt.offset+3], data[cdt.offset+4], data[cdt.offset+5])) != 0)\r
443                     {\r
444                         if (errorList != null)\r
445                         {\r
446                             errorList.addError(ErrorHandler.MINOR_ERROR, "Erroneous MARC8 multibyte character, Discarding bad character and continuing reading Multibyte characters");\r
447                             sb.append("[?]");\r
448                             cdt.offset += 3;\r
449                         }\r
450                     }\r
451                     else if (cdt.offset + 4 <= data.length && data[cdt.offset] > 0x7f && \r
452                             getMBChar(makeMultibyte(data[cdt.offset+1], data[cdt.offset+2], data[cdt.offset+3])) != 0)\r
453                     {\r
454                         if (errorList != null)\r
455                         {\r
456                             errorList.addError(ErrorHandler.MINOR_ERROR, "Erroneous character in MARC8 multibyte character, Copying bad character and continuing reading Multibyte characters");\r
457                             sb.append(getChar(data[cdt.offset], 0x42, 0x45));\r
458                             cdt.offset += 1;\r
459                         }\r
460                     }\r
461                     else\r
462                     {\r
463                         if (errorList != null)\r
464                         {\r
465                             errorList.addError(ErrorHandler.MINOR_ERROR, "Erroneous MARC8 multibyte character, inserting change to default character set");\r
466                         }\r
467                         cdt.multibyte = false;\r
468                         cdt.g0 = 0x42;\r
469                         cdt.g1 = 0x45;\r
470                     }\r
471                 } \r
472                 else if (errorList != null && cdt.offset + 4 <= data.length && ( data[cdt.offset+1] == 0x20 || data[cdt.offset+2]== 0x20)) \r
473                 {\r
474                     int multiByte = makeMultibyte( data[cdt.offset], ((data[cdt.offset+1] != 0x20)? data[cdt.offset+1] : data[cdt.offset+2]),  data[cdt.offset+3]);\r
475                     char c = getMBChar(multiByte);\r
476                     if (c != 0) \r
477                     {\r
478                         if (errorList != null)\r
479                         {\r
480                             errorList.addError(ErrorHandler.ERROR_TYPO, "Extraneous space found within MARC8 multibyte character");\r
481                         }\r
482                         sb.append(c);\r
483                         sb.append(' ');\r
484                         cdt.offset += 4;\r
485                     }\r
486                     else\r
487                     {\r
488                         if (errorList != null)\r
489                         {\r
490                             errorList.addError(ErrorHandler.MINOR_ERROR, "Erroneous MARC8 multibyte character, inserting change to default character set");\r
491                         }\r
492                         cdt.multibyte = false;\r
493                         cdt.g0 = 0x42;\r
494                         cdt.g1 = 0x45;\r
495                     }\r
496                 } \r
497                 else if (cdt.offset + 3 > data.length) \r
498                 {\r
499                     if (errorList != null)\r
500                     {\r
501                         errorList.addError(ErrorHandler.MINOR_ERROR, "Partial MARC8 multibyte character, inserting change to default character set");\r
502                         cdt.multibyte = false;\r
503                         cdt.g0 = 0x42;\r
504                         cdt.g1 = 0x45;\r
505                     }\r
506                     // if a field ends with an incomplete encoding of a multibyte character\r
507                     // simply discard that final partial character.\r
508                     else \r
509                     {\r
510                         cdt.offset += 3;\r
511                     }\r
512                 } \r
513             }\r
514             else \r
515             {\r
516                 char c = getChar(data[cdt.offset], cdt.g0, cdt.g1);\r
517                 if (c != 0) sb.append(c);\r
518                 else \r
519                 {\r
520                     String val = "0000"+Integer.toHexString((int)(data[cdt.offset]));\r
521                     sb.append("<U+"+ (val.substring(val.length()-4, val.length()))+ ">" );\r
522                 }\r
523                 cdt.offset += 1;\r
524             }\r
525             if (hasNext(cdt.offset, len))\r
526             {\r
527                 checkMode(data, cdt);\r
528             }\r
529         }\r
530         return sb.toString();\r
531     }\r
532 \r
533     @SuppressWarnings("unused")\r
534     private int makeMultibyte(char[] data) {\r
535         int[] chars = new int[3];\r
536         chars[0] = data[0] << 16;\r
537         chars[1] = data[1] << 8;\r
538         chars[2] = data[2];\r
539         return chars[0] | chars[1] | chars[2];\r
540     }\r
541     \r
542     public int makeMultibyte(char c1, char c2, char c3) \r
543     {\r
544         int[] chars = new int[3];\r
545         chars[0] = c1 << 16;\r
546         chars[1] = c2 << 8;\r
547         chars[2] = c3;\r
548         return chars[0] | chars[1] | chars[2];\r
549     }\r
550 \r
551     private char getChar(int ch, int g0, int g1) {\r
552         if (ch <= 0x7E)\r
553             return ct.getChar(ch, g0);\r
554         else\r
555             return ct.getChar(ch, g1);\r
556     }\r
557 \r
558     public char getMBChar(int ch) {\r
559         return ct.getChar(ch, 0x31);\r
560     }\r
561 \r
562     private static boolean hasNext(int pos, int len) {\r
563         if (pos < (len - 1))\r
564             return true;\r
565         return false;\r
566     }\r
567 \r
568     private static boolean isEscape(int i) {\r
569         if (i == 0x1B)\r
570             return true;\r
571         return false;\r
572     }\r
573 \r
574 }