Home | History | Annotate | Download | only in localeconverter
      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 
    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 
    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 
    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 
    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      */
    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     }
    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 
    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 
    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 
    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 
    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
    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;
    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         }
    628         public void write(OutputStream writer, int numIndent, boolean bare){
    629             while(next!=null){
    630                 next.write(writer, numIndent+1, false);
    631             }
    632         }
    633         public void writeIndent(OutputStream writer, int numIndent){
    634             for(int i=0; i< numIndent; i++){
    635                 write(writer,INDENT);
    636             }
    637         }
    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         }
    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;
    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;
    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;
    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;
    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;
    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;
    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;
    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 
    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 
    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 
    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 
    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     }
    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     }
    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 
    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 
   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     }
   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     }
   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 
   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 
   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 
   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 
   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