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