1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html#License
3 /*
4  ******************************************************************************
5  * Copyright (C) 2003-2013, International Business Machines Corporation and   *
6  * others. All Rights Reserved.                                               *
7  ******************************************************************************
8  */
9 
10 package com.ibm.icu.dev.tool.localeconverter;
11 
12 import java.io.BufferedOutputStream;
13 import java.io.File;
14 import java.io.FileOutputStream;
15 import java.io.IOException;
16 import java.io.OutputStream;
17 import java.text.MessageFormat;
18 import java.util.Date;
19 
20 import javax.xml.XMLConstants;
21 import javax.xml.parsers.DocumentBuilder;
22 import javax.xml.parsers.DocumentBuilderFactory;
23 import javax.xml.validation.Schema;
24 import javax.xml.validation.SchemaFactory;
25 
26 import org.w3c.dom.Document;
27 import org.w3c.dom.NamedNodeMap;
28 import org.w3c.dom.Node;
29 import org.w3c.dom.NodeList;
30 import org.xml.sax.ErrorHandler;
31 import org.xml.sax.InputSource;
32 import org.xml.sax.SAXException;
33 import org.xml.sax.SAXParseException;
34 
35 import com.ibm.icu.dev.tool.UOption;
36 
37 public final class XLIFF2ICUConverter {
38 
39     /**
40      * These must be kept in sync with getOptions().
41      */
42     private static final int HELP1 = 0;
43     private static final int HELP2 = 1;
44     private static final int SOURCEDIR = 2;
45     private static final int DESTDIR = 3;
46     private static final int TARGETONLY = 4;
47     private static final int SOURCEONLY = 5;
48     private static final int MAKE_SOURCE_ROOT = 6;
49     private static final int XLIFF_1_0 = 7;
50 
51     private static final UOption[] options = new UOption[] {
52         UOption.HELP_H(),
53         UOption.HELP_QUESTION_MARK(),
54         UOption.SOURCEDIR(),
55         UOption.DESTDIR(),
56         UOption.create("target-only", 't', UOption.OPTIONAL_ARG),
57         UOption.create("source-only", 'c', UOption.OPTIONAL_ARG),
58         UOption.create("make-source-root", 'r', UOption.NO_ARG),
59         UOption.create("xliff-1.0", 'x', UOption.NO_ARG)
60     };
61 
62     private static final int ARRAY_RESOURCE     = 0;
63     private static final int ALIAS_RESOURCE     = 1;
64     private static final int BINARY_RESOURCE    = 2;
65     private static final int INTEGER_RESOURCE   = 3;
66     private static final int INTVECTOR_RESOURCE = 4;
67     private static final int TABLE_RESOURCE     = 5;
68 
69     private static final String NEW_RESOURCES[] = {
70         "x-icu-array",
71         "x-icu-alias",
72         "x-icu-binary",
73         "x-icu-integer",
74         "x-icu-intvector",
75         "x-icu-table"
76     };
77 
78     private static final String OLD_RESOURCES[] = {
79         "array",
80         "alias",
81         "bin",
82         "int",
83         "intvector",
84         "table"
85     };
86 
87     private String resources[];
88 
89     private static final String ROOT            = "root";
90     private static final String RESTYPE         = "restype";
91     private static final String RESNAME         = "resname";
92     //private static final String YES             = "yes";
93     //private static final String NO              = "no";
94     private static final String TRANSLATE       = "translate";
95     //private static final String BODY            = "body";
96     private static final String GROUPS          = "group";
97     private static final String FILES           = "file";
98     private static final String TRANSUNIT       = "trans-unit";
99     private static final String BINUNIT         = "bin-unit";
100     private static final String BINSOURCE       = "bin-source";
101     //private static final String TS              = "ts";
102     //private static final String ORIGINAL        = "original";
103     private static final String SOURCELANGUAGE  = "source-language";
104     private static final String TARGETLANGUAGE  = "target-language";
105     private static final String TARGET          = "target";
106     private static final String SOURCE          = "source";
107     private static final String NOTE            = "note";
108     private static final String XMLLANG         = "xml:lang";
109     private static final String FILE            = "file";
110     private static final String INTVECTOR       = "intvector";
111     private static final String ARRAYS          = "array";
112     private static final String STRINGS         = "string";
113     private static final String BIN             = "bin";
114     private static final String INTS            = "int";
115     private static final String TABLE           = "table";
116     private static final String IMPORT          = "import";
117     private static final String HREF            = "href";
118     private static final String EXTERNALFILE    = "external-file";
119     private static final String INTERNALFILE    = "internal-file";
120     private static final String ALTTRANS        = "alt-trans";
121     private static final String CRC             = "crc";
122     private static final String ALIAS           = "alias";
123     private static final String LINESEP         = System.getProperty("line.separator");
124     private static final String BOM             = "\uFEFF";
125     private static final String CHARSET         = "UTF-8";
126     private static final String OPENBRACE       = "{";
127     private static final String CLOSEBRACE      = "}";
128     private static final String COLON           = ":";
129     private static final String COMMA           = ",";
130     private static final String QUOTE           = "\"";
131     private static final String COMMENTSTART    = "/**";
132     private static final String COMMENTEND      = " */";
133     private static final String TAG             = " * @";
134     private static final String COMMENTMIDDLE   = " * ";
135     private static final String SPACE           = " ";
136     private static final String INDENT          = "    ";
137     private static final String EMPTY           = "";
138     private static final String ID              = "id";
139 
main(String[] args)140     public static void main(String[] args) {
141         XLIFF2ICUConverter cnv = new XLIFF2ICUConverter();
142         cnv.processArgs(args);
143     }
144     private String    sourceDir      = null;
145     //private String    fileName       = null;
146     private String    destDir        = null;
147     private boolean   targetOnly     = false;
148     private String    targetFileName = null;
149     private boolean   makeSourceRoot = false;
150     private String    sourceFileName = null;
151     private boolean   sourceOnly     = false;
152     private boolean   xliff10        = false;
153 
processArgs(String[] args)154     private void processArgs(String[] args) {
155         int remainingArgc = 0;
156         try{
157             remainingArgc = UOption.parseArgs(args, options);
158         }catch (Exception e){
159             System.err.println("ERROR: "+ e.toString());
160             usage();
161         }
162         if(args.length==0 || options[HELP1].doesOccur || options[HELP2].doesOccur) {
163             usage();
164         }
165         if(remainingArgc==0){
166             System.err.println("ERROR: Either the file name to be processed is not "+
167                                "specified or the it is specified after the -t/-c \n"+
168                                "option which has an optional argument. Try rearranging "+
169                                "the options.");
170             usage();
171         }
172         if(options[SOURCEDIR].doesOccur) {
173             sourceDir = options[SOURCEDIR].value;
174         }
175 
176         if(options[DESTDIR].doesOccur) {
177             destDir = options[DESTDIR].value;
178         }
179 
180         if(options[TARGETONLY].doesOccur){
181             targetOnly = true;
182             targetFileName = options[TARGETONLY].value;
183         }
184 
185         if(options[SOURCEONLY].doesOccur){
186             sourceOnly = true;
187             sourceFileName = options[SOURCEONLY].value;
188         }
189 
190         if(options[MAKE_SOURCE_ROOT].doesOccur){
191             makeSourceRoot = true;
192         }
193 
194         if(options[XLIFF_1_0].doesOccur) {
195             xliff10 = true;
196         }
197 
198         if(destDir==null){
199             destDir = ".";
200         }
201 
202         if(sourceOnly == true && targetOnly == true){
203             System.err.println("--source-only and --target-only are specified. Please check the arguments and try again.");
204             usage();
205         }
206 
207         for (int i = 0; i < remainingArgc; i++) {
208             //int lastIndex = args[i].lastIndexOf(File.separator, args[i].length()) + 1; /* add 1 to skip past the separator */
209             //fileName = args[i].substring(lastIndex, args[i].length());
210             String xmlfileName = getFullPath(false,args[i]);
211             System.out.println("Processing file: "+xmlfileName);
212             createRB(xmlfileName);
213         }
214     }
215 
usage()216     private void usage() {
217         System.out.println("\nUsage: XLIFF2ICUConverter [OPTIONS] [FILES]\n\n"+
218             "This program is used to convert XLIFF files to ICU ResourceBundle TXT files.\n"+
219             "Please refer to the following options. Options are not case sensitive.\n"+
220             "Options:\n"+
221             "-s or --sourcedir          source directory for files followed by path, default is current directory.\n" +
222             "-d or --destdir            destination directory, followed by the path, default is current directory.\n" +
223             "-h or -? or --help         this usage text.\n"+
224             "-t or --target-only        only generate the target language txt file, followed by optional output file name.\n" +
225             "                           Cannot be used in conjunction with --source-only.\n"+
226             "-c or --source-only        only generate the source language bundle followed by optional output file name.\n"+
227             "                           Cannot be used in conjunction with --target-only.\n"+
228             "-r or --make-source-root   produce root bundle from source elements.\n" +
229             "-x or --xliff-1.0          source file is XLIFF 1.0" +
230             "example: com.ibm.icu.dev.tool.localeconverter.XLIFF2ICUConverter -t <optional argument> -s xxx -d yyy myResources.xlf");
231         System.exit(-1);
232     }
233 
getFullPath(boolean fileType, String fName)234     private String getFullPath(boolean fileType, String fName){
235         String str;
236         int lastIndex1 = fName.lastIndexOf(File.separator, fName.length()) + 1; /*add 1 to skip past the separator*/
237         int lastIndex2 = fName.lastIndexOf('.', fName.length());
238         if (fileType == true) {
239             if(lastIndex2 == -1){
240                 fName = fName.trim() + ".txt";
241             }else{
242                 if(!fName.substring(lastIndex2).equalsIgnoreCase(".txt")){
243                     fName =  fName.substring(lastIndex1,lastIndex2) + ".txt";
244                 }
245             }
246             if (destDir != null && fName != null) {
247                 str = destDir + File.separator + fName.trim();
248             } else {
249                 str = System.getProperty("user.dir") + File.separator + fName.trim();
250             }
251         } else {
252             if(lastIndex2 == -1){
253                 fName = fName.trim() + ".xlf";
254             }else{
255                 if(!fName.substring(lastIndex2).equalsIgnoreCase(".xml") && fName.substring(lastIndex2).equalsIgnoreCase(".xlf")){
256                     fName = fName.substring(lastIndex1,lastIndex2) + ".xlf";
257                 }
258             }
259             if(sourceDir != null && fName != null) {
260                 str = sourceDir + File.separator + fName;
261             } else if (lastIndex1 > 0) {
262                 str = fName;
263             } else {
264                 str = System.getProperty("user.dir") + File.separator + fName;
265             }
266         }
267         return str;
268     }
269 
270     /*
271      * Utility method to translate a String filename to URL.
272      *
273      * Note: This method is not necessarily proven to get the
274      * correct URL for every possible kind of filename; it should
275      * be improved.  It handles the most common cases that we've
276      * encountered when running Conformance tests on Xalan.
277      * Also note, this method does not handle other non-file:
278      * flavors of URLs at all.
279      *
280      * If the name is null, return null.
281      * If the name starts with a common URI scheme (namely the ones
282      * found in the examples of RFC2396), then simply return the
283      * name as-is (the assumption is that it's already a URL)
284      * Otherwise we attempt (cheaply) to convert to a file:/// URL.
285      */
filenameToURL(String filename)286     private static String filenameToURL(String filename){
287         // null begets null - something like the commutative property
288         if (null == filename){
289             return null;
290         }
291 
292         // Don't translate a string that already looks like a URL
293         if (filename.startsWith("file:")
294             || filename.startsWith("http:")
295             || filename.startsWith("ftp:")
296             || filename.startsWith("gopher:")
297             || filename.startsWith("mailto:")
298             || filename.startsWith("news:")
299             || filename.startsWith("telnet:")
300            ){
301                return filename;
302            }
303 
304 
305         File f = new File(filename);
306         String tmp = null;
307         try{
308             // This normally gives a better path
309             tmp = f.getCanonicalPath();
310         }catch (IOException ioe){
311             // But this can be used as a backup, for cases
312             //  where the file does not exist, etc.
313             tmp = f.getAbsolutePath();
314         }
315 
316         // URLs must explicitly use only forward slashes
317         if (File.separatorChar == '\\') {
318             tmp = tmp.replace('\\', '/');
319         }
320         // Note the presumption that it's a file reference
321         // Ensure we have the correct number of slashes at the
322         //  start: we always want 3 /// if it's absolute
323         //  (which we should have forced above)
324         if (tmp.startsWith("/")){
325             return "file://" + tmp;
326         }
327         else{
328             return "file:///" + tmp;
329         }
330     }
isXmlLang(String lang)331     private boolean isXmlLang (String lang){
332 
333         int suffix;
334         char c;
335 
336         if (lang.length () < 2){
337             return false;
338         }
339 
340         c = lang.charAt(1);
341         if (c == '-') {
342             c = lang.charAt(0);
343             if (!(c == 'i' || c == 'I' || c == 'x' || c == 'X')){
344                 return false;
345             }
346             suffix = 1;
347         } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
348             c = lang.charAt(0);
349             if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))){
350                 return false;
351             }
352             suffix = 2;
353         } else{
354             return false;
355         }
356         while (suffix < lang.length ()) {
357             c = lang.charAt(suffix);
358             if (c != '-'){
359                 break;
360             }
361             while (++suffix < lang.length ()) {
362                 c = lang.charAt(suffix);
363                 if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))){
364                     break;
365                 }
366             }
367         }
368         return  ((lang.length() == suffix) && (c != '-'));
369     }
370 
createRB(String xmlfileName)371     private void createRB(String xmlfileName) {
372 
373         String urls = filenameToURL(xmlfileName);
374         DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
375         dfactory.setNamespaceAware(true);
376         Document doc = null;
377 
378         if (xliff10) {
379             dfactory.setValidating(true);
380             resources = OLD_RESOURCES;
381         } else {
382             try {
383                 SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
384                 Schema schema = schemaFactory.newSchema();
385 
386                 dfactory.setSchema(schema);
387             } catch (SAXException e) {
388                 System.err.println("Can't create the schema...");
389                 System.exit(-1);
390             } catch (UnsupportedOperationException e) {
391                 System.err.println("ERROR:\tOne of the schema operations is not supported with this JVM.");
392                 System.err.println("\tIf you are using GNU Java, you should try using the latest Sun JVM.");
393                 System.err.println("\n*Here is the stack trace:");
394                 e.printStackTrace();
395                 System.exit(-1);
396             }
397 
398             resources = NEW_RESOURCES;
399         }
400 
401         ErrorHandler nullHandler = new ErrorHandler() {
402             public void warning(SAXParseException e) throws SAXException {
403 
404             }
405             public void error(SAXParseException e) throws SAXException {
406                 System.err.println("The XLIFF document is invalid, please check it first: ");
407                 System.err.println("Line "+e.getLineNumber()+", Column "+e.getColumnNumber());
408                 System.err.println("Error: " + e.getMessage());
409                 System.exit(-1);
410             }
411             public void fatalError(SAXParseException e) throws SAXException {
412                 throw e;
413             }
414         };
415 
416         try {
417             DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
418             docBuilder.setErrorHandler(nullHandler);
419             doc = docBuilder.parse(new InputSource(urls));
420 
421             NodeList nlist = doc.getElementsByTagName(FILES);
422             if(nlist.getLength()>1){
423                 throw new RuntimeException("Multiple <file> elements in the XLIFF file not supported.");
424             }
425 
426             // get the value of source-language attribute
427             String sourceLang = getLanguageName(doc, SOURCELANGUAGE);
428             // get the value of target-language attribute
429             String targetLang = getLanguageName(doc, TARGETLANGUAGE);
430 
431             // get the list of <source> elements
432             NodeList sourceList = doc.getElementsByTagName(SOURCE);
433             // get the list of target elements
434             NodeList targetList = doc.getElementsByTagName(TARGET);
435 
436             // check if the xliff file has source elements in multiple languages
437             // the source-language value should be the same as xml:lang values
438             // of all the source elements.
439             String xmlSrcLang = checkLangAttribute(sourceList, sourceLang);
440 
441             // check if the xliff file has target elements in multiple languages
442             // the target-language value should be the same as xml:lang values
443             // of all the target elements.
444             String xmlTargetLang = checkLangAttribute(targetList, targetLang);
445 
446             // Create the Resource linked list which will hold the
447             // source and target bundles after parsing
448             Resource[] set = new Resource[2];
449             set[0] = new ResourceTable();
450             set[1] = new ResourceTable();
451 
452             // lenient extraction of source language
453             if(makeSourceRoot == true){
454                 set[0].name = ROOT;
455             }else if(sourceLang!=null){
456                 set[0].name = sourceLang.replace('-','_');
457             }else{
458                 if(xmlSrcLang != null){
459                     set[0].name = xmlSrcLang.replace('-','_');
460                 }else{
461                     System.err.println("ERROR: Could not figure out the source language of the file. Please check the XLIFF file.");
462                     System.exit(-1);
463                 }
464             }
465 
466             // lenient extraction of the target language
467             if(targetLang!=null){
468                 set[1].name = targetLang.replace('-','_');
469             }else{
470                 if(xmlTargetLang!=null){
471                     set[1].name = xmlTargetLang.replace('-','_');
472                 }else{
473                     System.err.println("WARNING: Could not figure out the target language of the file. Producing source bundle only.");
474                 }
475             }
476 
477 
478             // check if any <alt-trans> elements are present
479             NodeList altTrans = doc.getElementsByTagName(ALTTRANS);
480             if(altTrans.getLength()>0){
481                 System.err.println("WARNING: <alt-trans> elements in found. Ignoring all <alt-trans> elements.");
482             }
483 
484             // get all the group elements
485             NodeList list = doc.getElementsByTagName(GROUPS);
486 
487             // process the first group element. The first group element is
488             // the base table that must be parsed recursively
489             parseTable(list.item(0), set);
490 
491             // write out the bundle
492             writeResource(set, xmlfileName);
493          }
494         catch (Throwable se) {
495             System.err.println("ERROR: " + se.toString());
496             System.exit(1);
497         }
498     }
499 
writeResource(Resource[] set, String xmlfileName)500     private void writeResource(Resource[] set, String xmlfileName){
501         if(targetOnly==false){
502             writeResource(set[0], xmlfileName, sourceFileName);
503         }
504         if(sourceOnly == false){
505             if(targetOnly==true && set[1].name == null){
506                 throw new RuntimeException("The "+ xmlfileName +" does not contain translation\n");
507             }
508             if(set[1].name != null){
509                 writeResource(set[1], xmlfileName, targetFileName);
510             }
511         }
512     }
513 
writeResource(Resource set, String sourceFilename, String targetFilename)514     private void writeResource(Resource set, String sourceFilename, String targetFilename){
515         try {
516             String outputFileName = null;
517             if(targetFilename != null){
518                 outputFileName = destDir+File.separator+targetFilename+".txt";
519             }else{
520                 outputFileName = destDir+File.separator+set.name+".txt";
521             }
522             FileOutputStream file = new FileOutputStream(outputFileName);
523             BufferedOutputStream writer = new BufferedOutputStream(file);
524 
525             writeHeader(writer,sourceFilename);
526 
527             //Now start writing the resource;
528             Resource current = set;
529             while(current!=null){
530                 current.write(writer, 0, false);
531                 current = current.next;
532             }
533             writer.flush();
534             writer.close();
535         } catch (Exception ie) {
536             System.err.println("ERROR :" + ie.toString());
537             return;
538         }
539     }
540 
getLanguageName(Document doc, String lang)541     private String getLanguageName(Document doc, String lang){
542         if(doc!=null){
543             NodeList list = doc.getElementsByTagName(FILE);
544             Node node = list.item(0);
545             NamedNodeMap attr = node.getAttributes();
546             Node orig = attr.getNamedItem(lang);
547 
548             if(orig != null){
549                 String name = orig.getNodeValue();
550                 NodeList groupList = doc.getElementsByTagName(GROUPS);
551                 Node group = groupList.item(0);
552                 NamedNodeMap groupAtt = group.getAttributes();
553                 Node id = groupAtt.getNamedItem(ID);
554                 if(id!=null){
555                     String idVal = id.getNodeValue();
556 
557                     if(!name.equals(idVal)){
558                         System.out.println("WARNING: The id value != language name. " +
559                                            "Please compare the output with the orignal " +
560                                            "ICU ResourceBundle before proceeding.");
561                     }
562                 }
563                 if(!isXmlLang(name)){
564                     System.err.println("The attribute "+ lang + "=\""+ name +
565                                        "\" of <file> element is invalid.");
566                     System.exit(-1);
567                 }
568                 return name;
569             }
570         }
571         return null;
572     }
573 
574     // check if the xliff file is translated into multiple languages
575     // The XLIFF specification allows for single <target> element
576     // as the child of <trans-unit> but the attributes of the
577     // <target> element may different across <trans-unit> elements
578     // check for it. Similar is the case with <source> elements
checkLangAttribute(NodeList list, String origName)579     private String checkLangAttribute(NodeList list, String origName){
580         String oldLangName=origName;
581         for(int i = 0 ;i<list.getLength(); i++){
582             Node node = list.item(i);
583             NamedNodeMap attr = node.getAttributes();
584             Node lang = attr.getNamedItem(XMLLANG);
585             String langName = null;
586             // the target element should always contain xml:lang attribute
587             if(lang==null ){
588                 if(origName==null){
589                     System.err.println("Encountered <target> element without xml:lang attribute. Please fix the below element in the XLIFF file.\n"+ node.toString());
590                     System.exit(-1);
591                 }else{
592                     langName = origName;
593                 }
594             }else{
595                 langName = lang.getNodeValue();
596             }
597 
598             if(oldLangName!=null && langName!=null && !langName.equals(oldLangName)){
599                 throw new RuntimeException("The <trans-unit> elements must be bilingual, multilingual tranlations not supported. xml:lang = " + oldLangName +
600                                            " and xml:lang = " + langName);
601             }
602             oldLangName = langName;
603         }
604         return oldLangName;
605     }
606 
607     private class Resource{
608         String[] note = new String[20];
609         int noteLen = 0;
610         String translate;
611         String comment;
612         String name;
613         Resource next;
escapeSyntaxChars(String val)614         public String escapeSyntaxChars(String val){
615             // escape the embedded quotes
616             char[] str = val.toCharArray();
617             StringBuffer result = new StringBuffer();
618             for(int i=0; i<str.length; i++){
619                 switch (str[i]){
620                     case '\u0022':
621                         result.append('\\'); //append backslash
622                     default:
623                         result.append(str[i]);
624                 }
625             }
626             return result.toString();
627         }
write(OutputStream writer, int numIndent, boolean bare)628         public void write(OutputStream writer, int numIndent, boolean bare){
629             while(next!=null){
630                 next.write(writer, numIndent+1, false);
631             }
632         }
writeIndent(OutputStream writer, int numIndent)633         public void writeIndent(OutputStream writer, int numIndent){
634             for(int i=0; i< numIndent; i++){
635                 write(writer,INDENT);
636             }
637         }
write(OutputStream writer, String value)638         public void write(OutputStream writer, String value){
639             try {
640                 byte[] bytes = value.getBytes(CHARSET);
641                 writer.write(bytes, 0, bytes.length);
642             } catch(Exception e) {
643                 System.err.println(e);
644                 System.exit(1);
645             }
646         }
writeComments(OutputStream writer, int numIndent)647         public void writeComments(OutputStream writer, int numIndent){
648             boolean translateIsDefault = translate == null || translate.equals("yes");
649 
650             if(comment!=null || ! translateIsDefault || noteLen > 0){
651                 // print the start of the comment
652                 writeIndent(writer, numIndent);
653                 write(writer, COMMENTSTART+LINESEP);
654 
655                 // print comment if any
656                 if(comment!=null){
657                     writeIndent(writer, numIndent);
658                     write(writer, COMMENTMIDDLE);
659                     write(writer, comment);
660                     write(writer, LINESEP);
661                 }
662 
663                 // print the translate attribute if any
664                 if(! translateIsDefault){
665                     writeIndent(writer, numIndent);
666                     write(writer, TAG+TRANSLATE+SPACE);
667                     write(writer, translate);
668                     write(writer, LINESEP);
669                 }
670 
671                 // print note elements if any
672                 for(int i=0; i<noteLen; i++){
673                     if(note[i]!=null){
674                         writeIndent(writer, numIndent);
675                         write(writer, TAG+NOTE+SPACE+note[i]);
676                         write(writer, LINESEP);
677                     }
678                 }
679 
680                 // terminate the comment
681                 writeIndent(writer, numIndent);
682                 write(writer, COMMENTEND+LINESEP);
683             }
684         }
685     }
686 
687     private class ResourceString extends Resource{
688         String val;
write(OutputStream writer, int numIndent, boolean bare)689         public void write(OutputStream writer, int numIndent, boolean bare){
690             writeComments(writer, numIndent);
691             writeIndent(writer, numIndent);
692             if(bare==true){
693                 if(name!=null){
694                     throw new RuntimeException("Bare option is set to true but the resource has a name!");
695                 }
696 
697                 write(writer,QUOTE+escapeSyntaxChars(val)+QUOTE);
698             }else{
699                 write(writer, name+COLON+STRINGS+ OPENBRACE + QUOTE + escapeSyntaxChars(val) + QUOTE+ CLOSEBRACE + LINESEP);
700             }
701         }
702     }
703     private class ResourceAlias extends Resource{
704         String val;
write(OutputStream writer, int numIndent, boolean bare)705         public void write(OutputStream writer, int numIndent, boolean bare){
706             writeComments(writer, numIndent);
707             writeIndent(writer, numIndent);
708             String line =  ((name==null)? EMPTY: name)+COLON+ALIAS+ OPENBRACE+QUOTE+escapeSyntaxChars(val)+QUOTE+CLOSEBRACE;
709             if(bare==true){
710                 if(name!=null){
711                     throw new RuntimeException("Bare option is set to true but the resource has a name!");
712                 }
713                 write(writer,line);
714             }else{
715                 write(writer, line+LINESEP);
716             }
717         }
718     }
719     private class ResourceInt extends Resource{
720         String val;
write(OutputStream writer, int numIndent, boolean bare)721         public void write(OutputStream writer, int numIndent, boolean bare){
722             writeComments(writer, numIndent);
723             writeIndent(writer, numIndent);
724             String line =  ((name==null)? EMPTY: name)+COLON+INTS+ OPENBRACE + val +CLOSEBRACE;
725             if(bare==true){
726                 if(name!=null){
727                     throw new RuntimeException("Bare option is set to true but the resource has a name!");
728                 }
729                 write(writer,line);
730             }else{
731                 write(writer, line+LINESEP);
732             }
733         }
734     }
735     private class ResourceBinary extends Resource{
736         String internal;
737         String external;
write(OutputStream writer, int numIndent, boolean bare)738         public void write(OutputStream writer, int numIndent, boolean bare){
739             writeComments(writer, numIndent);
740             writeIndent(writer, numIndent);
741             if(internal==null){
742                 String line = ((name==null) ? EMPTY : name)+COLON+IMPORT+ OPENBRACE+QUOTE+external+QUOTE+CLOSEBRACE + ((bare==true) ?  EMPTY : LINESEP);
743                 write(writer, line);
744             }else{
745                 String line = ((name==null) ? EMPTY : name)+COLON+BIN+ OPENBRACE+internal+CLOSEBRACE+ ((bare==true) ?  EMPTY : LINESEP);
746                 write(writer,line);
747             }
748 
749         }
750     }
751     private class ResourceIntVector extends Resource{
752         ResourceInt first;
write(OutputStream writer, int numIndent, boolean bare)753         public void write(OutputStream writer, int numIndent, boolean bare){
754             writeComments(writer, numIndent);
755             writeIndent(writer, numIndent);
756             write(writer, name+COLON+INTVECTOR+OPENBRACE+LINESEP);
757             numIndent++;
758             ResourceInt current = (ResourceInt) first;
759             while(current != null){
760                 //current.write(writer, numIndent, true);
761                 writeIndent(writer, numIndent);
762                 write(writer, current.val);
763                 write(writer, COMMA+LINESEP);
764                 current = (ResourceInt) current.next;
765             }
766             numIndent--;
767             writeIndent(writer, numIndent);
768             write(writer, CLOSEBRACE+LINESEP);
769         }
770     }
771     private class ResourceTable extends Resource{
772         Resource first;
write(OutputStream writer, int numIndent, boolean bare)773         public void write(OutputStream writer, int numIndent, boolean bare){
774             writeComments(writer, numIndent);
775             writeIndent(writer, numIndent);
776             write(writer, name+COLON+TABLE+OPENBRACE+LINESEP);
777             numIndent++;
778             Resource current = first;
779             while(current != null){
780                 current.write(writer, numIndent, false);
781                 current = current.next;
782             }
783             numIndent--;
784             writeIndent(writer, numIndent);
785             write(writer, CLOSEBRACE+LINESEP);
786         }
787     }
788     private class ResourceArray extends Resource{
789         Resource first;
write(OutputStream writer, int numIndent, boolean bare)790         public void write(OutputStream writer, int numIndent, boolean bare){
791             writeComments(writer, numIndent);
792             writeIndent(writer, numIndent);
793             write(writer, name+COLON+ARRAYS+OPENBRACE+LINESEP);
794             numIndent++;
795             Resource current = first;
796             while(current != null){
797                 current.write(writer, numIndent, true);
798                 write(writer, COMMA+LINESEP);
799                 current = current.next;
800             }
801             numIndent--;
802             writeIndent(writer, numIndent);
803             write(writer, CLOSEBRACE+LINESEP);
804         }
805     }
806 
getAttributeValue(Node sNode, String attribName)807     private String getAttributeValue(Node sNode, String attribName){
808         String value=null;
809         Node node = sNode;
810 
811         NamedNodeMap attributes = node.getAttributes();
812         Node attr = attributes.getNamedItem(attribName);
813         if(attr!=null){
814             value = attr.getNodeValue();
815         }
816 
817         return value;
818     }
819 
parseResourceString(Node node, ResourceString[] set)820     private void parseResourceString(Node node, ResourceString[] set){
821         ResourceString currentSource;
822         ResourceString currentTarget;
823         currentSource =  set[0];
824         currentTarget =  set[1];
825         String resName   = getAttributeValue(node, RESNAME);
826         String translate = getAttributeValue(node, TRANSLATE);
827 
828         // loop to pickup <source>, <note> and <target> elements
829         for(Node transUnit = node.getFirstChild(); transUnit!=null; transUnit = transUnit.getNextSibling()){
830             short type = transUnit.getNodeType();
831             String name = transUnit.getNodeName();
832             if(type == Node.COMMENT_NODE){
833                 // get the comment
834                currentSource.comment =  currentTarget.comment = transUnit.getNodeValue();
835             }else if( type == Node.ELEMENT_NODE){
836                 if(name.equals(SOURCE)){
837                     // save the source and target values
838                     currentSource.name = currentTarget.name = resName;
839                     currentSource.val = currentTarget.val = transUnit.getFirstChild().getNodeValue();
840                     currentSource.translate = currentTarget.translate = translate;
841                 }else if(name.equals(NOTE)){
842                     // save the note values
843                     currentSource.note[currentSource.noteLen++] =
844                         currentTarget.note[currentTarget.noteLen++] =
845                         transUnit.getFirstChild().getNodeValue();
846                 }else if(name.equals(TARGET)){
847                     // if there is a target element replace it
848                     currentTarget.val = transUnit.getFirstChild().getNodeValue();
849                 }
850             }
851 
852         }
853     }
854 
parseResourceInt(Node node, ResourceInt[] set)855     private void parseResourceInt(Node node, ResourceInt[] set){
856         ResourceInt currentSource;
857         ResourceInt currentTarget;
858         currentSource =  set[0];
859         currentTarget =  set[1];
860         String resName   = getAttributeValue(node, RESNAME);
861         String translate = getAttributeValue(node, TRANSLATE);
862         // loop to pickup <source>, <note> and <target> elements
863         for(Node transUnit = node.getFirstChild(); transUnit!=null; transUnit = transUnit.getNextSibling()){
864             short type = transUnit.getNodeType();
865             String name = transUnit.getNodeName();
866             if(type == Node.COMMENT_NODE){
867                 // get the comment
868                currentSource.comment =  currentTarget.comment = transUnit.getNodeValue();
869             }else if( type == Node.ELEMENT_NODE){
870                 if(name.equals(SOURCE)){
871                     // save the source and target values
872                     currentSource.name = currentTarget.name = resName;
873                     currentSource.translate = currentTarget.translate = translate;
874                     currentSource.val = currentTarget.val = transUnit.getFirstChild().getNodeValue();
875                 }else if(name.equals(NOTE)){
876                     // save the note values
877                     currentSource.note[currentSource.noteLen++] =
878                         currentTarget.note[currentTarget.noteLen++] =
879                         transUnit.getFirstChild().getNodeValue();
880                 }else if(name.equals(TARGET)){
881                     // if there is a target element replace it
882                     currentTarget.val = transUnit.getFirstChild().getNodeValue();
883                 }
884             }
885 
886         }
887     }
888 
parseResourceAlias(Node node, ResourceAlias[] set)889     private void parseResourceAlias(Node node, ResourceAlias[] set){
890         ResourceAlias currentSource;
891         ResourceAlias currentTarget;
892         currentSource =  set[0];
893         currentTarget =  set[1];
894         String resName   = getAttributeValue(node, RESNAME);
895         String translate = getAttributeValue(node, TRANSLATE);
896         // loop to pickup <source>, <note> and <target> elements
897         for(Node transUnit = node.getFirstChild(); transUnit!=null; transUnit = transUnit.getNextSibling()){
898             short type = transUnit.getNodeType();
899             String name = transUnit.getNodeName();
900             if(type == Node.COMMENT_NODE){
901                 // get the comment
902                currentSource.comment =  currentTarget.comment = transUnit.getNodeValue();
903             }else if( type == Node.ELEMENT_NODE){
904                 if(name.equals(SOURCE)){
905                     // save the source and target values
906                     currentSource.name = currentTarget.name = resName;
907                     currentSource.translate = currentTarget.translate = translate;
908                     currentSource.val = currentTarget.val = transUnit.getFirstChild().getNodeValue();
909                 }else if(name.equals(NOTE)){
910                     // save the note values
911                     currentSource.note[currentSource.noteLen++] =
912                         currentTarget.note[currentTarget.noteLen++] =
913                         transUnit.getFirstChild().getNodeValue();
914                 }else if(name.equals(TARGET)){
915                     // if there is a target element replace it
916                     currentTarget.val = transUnit.getFirstChild().getNodeValue();
917                 }
918             }
919 
920         }
921     }
parseResourceBinary(Node node, ResourceBinary[] set)922     private void parseResourceBinary(Node node, ResourceBinary[] set){
923         ResourceBinary currentSource;
924         ResourceBinary currentTarget;
925         currentSource =  set[0];
926         currentTarget =  set[1];
927 
928         // loop to pickup <source>, <note> and <target> elements
929         for(Node transUnit = node.getFirstChild(); transUnit!=null; transUnit = transUnit.getNextSibling()){
930             short type = transUnit.getNodeType();
931             String name = transUnit.getNodeName();
932             if(type == Node.COMMENT_NODE){
933                 // get the comment
934                currentSource.comment =  currentTarget.comment = transUnit.getNodeValue();
935             }else if( type == Node.ELEMENT_NODE){
936                 if(name.equals(BINSOURCE)){
937                     // loop to pickup internal-file/extenal-file element
938                     continue;
939                 }else if(name.equals(NOTE)){
940                     // save the note values
941                     currentSource.note[currentSource.noteLen++] =
942                         currentTarget.note[currentTarget.noteLen++] =
943                         transUnit.getFirstChild().getNodeValue();
944                 }else if(name.equals(INTERNALFILE)){
945                     // if there is a target element replace it
946                     String crc = getAttributeValue(transUnit, CRC);
947                     String value = transUnit.getFirstChild().getNodeValue();
948 
949                     //verify that the binary value conforms to the CRC
950                     if(Integer.parseInt(crc, 10) != CalculateCRC32.computeCRC32(value)) {
951                         System.err.println("ERROR: CRC value incorrect! Please check.");
952                         System.exit(1);
953                     }
954 
955                     currentTarget.internal = currentSource.internal= value;
956 
957                 }else if(name.equals(EXTERNALFILE)){
958                     currentSource.external = getAttributeValue(transUnit, HREF);
959                     currentTarget.external = currentSource.external;
960                 }
961             }
962 
963         }
964     }
parseTransUnit(Node node, Resource[] set)965     private void parseTransUnit(Node node, Resource[] set){
966 
967         String attrType = getAttributeValue(node, RESTYPE);
968         String translate = getAttributeValue(node, TRANSLATE);
969         if(attrType==null || attrType.equals(STRINGS)){
970             ResourceString[] strings = new ResourceString[2];
971             strings[0] = new ResourceString();
972             strings[1] = new ResourceString();
973             parseResourceString(node, strings);
974             strings[0].translate = strings[1].translate = translate;
975             set[0] = strings[0];
976             set[1] = strings[1];
977         }else if(attrType.equals(resources[INTEGER_RESOURCE])){
978             ResourceInt[] ints = new ResourceInt[2];
979             ints[0] = new ResourceInt();
980             ints[1] = new ResourceInt();
981             parseResourceInt(node, ints);
982             ints[0].translate = ints[1].translate = translate;
983             set[0] = ints[0];
984             set[1] = ints[1];
985         }else if(attrType.equals(resources[ALIAS_RESOURCE])){
986             ResourceAlias[] ints = new ResourceAlias[2];
987             ints[0] = new ResourceAlias();
988             ints[1] = new ResourceAlias();
989             parseResourceAlias(node, ints);
990             ints[0].translate = ints[1].translate = translate;
991             set[0] = ints[0];
992             set[1] = ints[1];
993         }
994     }
995 
parseBinUnit(Node node, Resource[] set)996     private void parseBinUnit(Node node, Resource[] set){
997         if (getAttributeValue(node, RESTYPE).equals(resources[BINARY_RESOURCE])) {
998             ResourceBinary[] bins = new ResourceBinary[2];
999 
1000             bins[0] = new ResourceBinary();
1001             bins[1] = new ResourceBinary();
1002 
1003             Resource currentSource = bins[0];
1004             Resource currentTarget = bins[1];
1005             String resName   = getAttributeValue(node, RESNAME);
1006             String translate = getAttributeValue(node, TRANSLATE);
1007 
1008             currentTarget.name = currentSource.name = resName;
1009             currentSource.translate = currentTarget.translate = translate;
1010 
1011             for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()){
1012                 short type = child.getNodeType();
1013                 String name = child.getNodeName();
1014 
1015                 if(type == Node.COMMENT_NODE){
1016                     currentSource.comment = currentTarget.comment = child.getNodeValue();
1017                 }else if(type == Node.ELEMENT_NODE){
1018                     if(name.equals(BINSOURCE)){
1019                         parseResourceBinary(child, bins);
1020                     }else if(name.equals(NOTE)){
1021                         String note =  child.getFirstChild().getNodeValue();
1022 
1023                         currentSource.note[currentSource.noteLen++] = currentTarget.note[currentTarget.noteLen++] = note;
1024                     }
1025                 }
1026             }
1027 
1028             set[0] = bins[0];
1029             set[1] = bins[1];
1030         }
1031     }
1032 
parseArray(Node node, Resource[] set)1033     private void parseArray(Node node, Resource[] set){
1034         if(set[0]==null){
1035             set[0] = new ResourceArray();
1036             set[1] = new ResourceArray();
1037         }
1038         Resource currentSource = set[0];
1039         Resource currentTarget = set[1];
1040         String resName = getAttributeValue(node, RESNAME);
1041         currentSource.name = currentTarget.name = resName;
1042         boolean isFirst = true;
1043 
1044         for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()){
1045             short type = child.getNodeType();
1046             String name = child.getNodeName();
1047             if(type == Node.COMMENT_NODE){
1048                 currentSource.comment = currentTarget.comment = child.getNodeValue();
1049             }else if(type == Node.ELEMENT_NODE){
1050                 if(name.equals(TRANSUNIT)){
1051                     Resource[] next = new Resource[2];
1052                     parseTransUnit(child, next);
1053                     if(isFirst==true){
1054                        ((ResourceArray) currentSource).first = next[0];
1055                        ((ResourceArray) currentTarget).first = next[1];
1056                        currentSource = ((ResourceArray) currentSource).first;
1057                        currentTarget = ((ResourceArray) currentTarget).first;
1058                        isFirst = false;
1059                     }else{
1060                         currentSource.next = next[0];
1061                         currentTarget.next = next[1];
1062                         // set the next pointers
1063                         currentSource = currentSource.next;
1064                         currentTarget = currentTarget.next;
1065                     }
1066                 }else if(name.equals(NOTE)){
1067                     String note =  child.getFirstChild().getNodeValue();
1068                     currentSource.note[currentSource.noteLen++] = currentTarget.note[currentTarget.noteLen++] = note;
1069                 }else if(name.equals(BINUNIT)){
1070                     Resource[] next = new Resource[2];
1071                     parseBinUnit(child, next);
1072                     if(isFirst==true){
1073                        ((ResourceArray) currentSource).first = next[0];
1074                        ((ResourceArray) currentTarget).first = next[1];
1075                        currentSource = ((ResourceArray) currentSource).first.next;
1076                        currentTarget = ((ResourceArray) currentTarget).first.next;
1077                        isFirst = false;
1078                     }else{
1079                         currentSource.next = next[0];
1080                         currentTarget.next = next[1];
1081                         // set the next pointers
1082                         currentSource = currentSource.next;
1083                         currentTarget = currentTarget.next;
1084                     }
1085                 }
1086             }
1087         }
1088     }
parseIntVector(Node node, Resource[] set)1089     private void parseIntVector(Node node, Resource[] set){
1090         if(set[0]==null){
1091             set[0] = new ResourceIntVector();
1092             set[1] = new ResourceIntVector();
1093         }
1094         Resource currentSource = set[0];
1095         Resource currentTarget = set[1];
1096         String resName = getAttributeValue(node, RESNAME);
1097         String translate = getAttributeValue(node,TRANSLATE);
1098         currentSource.name = currentTarget.name = resName;
1099         currentSource.translate = translate;
1100         boolean isFirst = true;
1101         for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()){
1102             short type = child.getNodeType();
1103             String name = child.getNodeName();
1104             if(type == Node.COMMENT_NODE){
1105                 currentSource.comment = currentTarget.comment = child.getNodeValue();
1106             }else if(type == Node.ELEMENT_NODE){
1107                 if(name.equals(TRANSUNIT)){
1108                     Resource[] next = new Resource[2];
1109                     parseTransUnit(child, next);
1110                     if(isFirst==true){
1111                         // the down cast should be safe .. if not something is terribly wrong!!
1112                        ((ResourceIntVector) currentSource).first = (ResourceInt)next[0];
1113                        ((ResourceIntVector) currentTarget).first = (ResourceInt) next[1];
1114                        currentSource = ((ResourceIntVector) currentSource).first;
1115                        currentTarget = ((ResourceIntVector) currentTarget).first;
1116                        isFirst = false;
1117                     }else{
1118                         currentSource.next = next[0];
1119                         currentTarget.next = next[1];
1120                         // set the next pointers
1121                         currentSource = currentSource.next;
1122                         currentTarget = currentTarget.next;
1123                     }
1124                 }else if(name.equals(NOTE)){
1125                     String note =  child.getFirstChild().getNodeValue();
1126                     currentSource.note[currentSource.noteLen++] = currentTarget.note[currentTarget.noteLen++] = note;
1127                 }
1128             }
1129         }
1130     }
parseTable(Node node, Resource[] set)1131     private void parseTable(Node node, Resource[] set){
1132         if(set[0]==null){
1133             set[0] = new ResourceTable();
1134             set[1] = new ResourceTable();
1135         }
1136         Resource currentSource = set[0];
1137         Resource currentTarget = set[1];
1138 
1139         String resName = getAttributeValue(node, RESNAME);
1140         String translate = getAttributeValue(node,TRANSLATE);
1141         if(resName!=null && currentSource.name==null && currentTarget.name==null){
1142             currentSource.name = currentTarget.name = resName;
1143         }
1144         currentTarget.translate = currentSource.translate = translate;
1145 
1146         boolean isFirst = true;
1147         for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()){
1148             short type = child.getNodeType();
1149             String name = child.getNodeName();
1150             if(type == Node.COMMENT_NODE){
1151                 currentSource.comment = currentTarget.comment = child.getNodeValue();
1152             }else if(type == Node.ELEMENT_NODE){
1153                 if(name.equals(GROUPS)){
1154                     Resource[] next = new Resource[2];
1155                     parseGroup(child, next);
1156                     if(isFirst==true){
1157                         // the down cast should be safe .. if not something is terribly wrong!!
1158                        ((ResourceTable) currentSource).first = next[0];
1159                        ((ResourceTable) currentTarget).first = next[1];
1160                        currentSource = ((ResourceTable) currentSource).first;
1161                        currentTarget = ((ResourceTable) currentTarget).first;
1162                        isFirst = false;
1163                     }else{
1164                         currentSource.next = next[0];
1165                         currentTarget.next = next[1];
1166                         // set the next pointers
1167                         currentSource = currentSource.next;
1168                         currentTarget = currentTarget.next;
1169                     }
1170                 }else if(name.equals(TRANSUNIT)){
1171                     Resource[] next = new Resource[2];
1172                     parseTransUnit(child, next);
1173                     if(isFirst==true){
1174                         // the down cast should be safe .. if not something is terribly wrong!!
1175                        ((ResourceTable) currentSource).first = next[0];
1176                        ((ResourceTable) currentTarget).first = next[1];
1177                        currentSource = ((ResourceTable) currentSource).first;
1178                        currentTarget = ((ResourceTable) currentTarget).first;
1179                        isFirst = false;
1180                     }else{
1181                         currentSource.next = next[0];
1182                         currentTarget.next = next[1];
1183                         // set the next pointers
1184                         currentSource = currentSource.next;
1185                         currentTarget = currentTarget.next;
1186                     }
1187                 }else if(name.equals(NOTE)){
1188                     String note =  child.getFirstChild().getNodeValue();
1189                     currentSource.note[currentSource.noteLen++] = currentTarget.note[currentTarget.noteLen++] = note;
1190                 }else if(name.equals(BINUNIT)){
1191                     Resource[] next = new Resource[2];
1192                     parseBinUnit(child, next);
1193                     if(isFirst==true){
1194                         // the down cast should be safe .. if not something is terribly wrong!!
1195                        ((ResourceTable) currentSource).first = next[0];
1196                        ((ResourceTable) currentTarget).first = next[1];
1197                        currentSource = ((ResourceTable) currentSource).first;
1198                        currentTarget = ((ResourceTable) currentTarget).first;
1199                        isFirst = false;
1200                     }else{
1201                         currentSource.next = next[0];
1202                         currentTarget.next = next[1];
1203                         // set the next pointers
1204                         currentSource = currentSource.next;
1205                         currentTarget = currentTarget.next;
1206                     }
1207                 }
1208             }
1209         }
1210     }
1211 
parseGroup(Node node, Resource[] set)1212     private void parseGroup(Node node, Resource[] set){
1213 
1214         // figure out what kind of group this is
1215         String resType = getAttributeValue(node, RESTYPE);
1216         if(resType.equals(resources[ARRAY_RESOURCE])){
1217             parseArray(node, set);
1218         }else if( resType.equals(resources[TABLE_RESOURCE])){
1219             parseTable(node, set);
1220         }else if( resType.equals(resources[INTVECTOR_RESOURCE])){
1221             parseIntVector(node, set);
1222         }
1223     }
1224 
1225 
writeLine(OutputStream writer, String line)1226     private void writeLine(OutputStream writer, String line) {
1227         try {
1228             byte[] bytes = line.getBytes(CHARSET);
1229             writer.write(bytes, 0, bytes.length);
1230         } catch (Exception e) {
1231             System.err.println(e);
1232             System.exit(1);
1233         }
1234     }
1235 
writeHeader(OutputStream writer, String fileName)1236     private void writeHeader(OutputStream writer, String fileName){
1237         final String header =
1238             "// ***************************************************************************" + LINESEP +
1239             "// *" + LINESEP +
1240             "// * Tool: com.ibm.icu.dev.tool.localeconverter.XLIFF2ICUConverter.java" + LINESEP +
1241             "// * Date & Time: {0,date,MM/dd/yyyy hh:mm:ss a z}"+ LINESEP +
1242             "// * Source File: {1}" + LINESEP +
1243             "// *" + LINESEP +
1244             "// ***************************************************************************" + LINESEP;
1245 
1246         writeBOM(writer);
1247         MessageFormat format = new MessageFormat(header);
1248         Object args[] = {new Date(System.currentTimeMillis()), fileName};
1249 
1250         writeLine(writer, format.format(args));
1251     }
1252 
writeBOM(OutputStream buffer)1253     private  void writeBOM(OutputStream buffer) {
1254         try {
1255             byte[] bytes = BOM.getBytes(CHARSET);
1256             buffer.write(bytes, 0, bytes.length);
1257         } catch(Exception e) {
1258             System.err.println(e);
1259             System.exit(1);
1260         }
1261     }
1262 }
1263