/*
 *******************************************************************************
 * Copyright (C) 2003-2012, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */
/**
 * @author Ram Viswanadha
 */
package org.unicode.cldr.icu;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;

import org.unicode.cldr.util.LDMLUtilities;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.Collator;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.Normalizer;
import com.ibm.icu.text.RuleBasedCollator;
import com.ibm.icu.util.ULocale;

public class LDMLComparator {
    /*
     * This application will compare different locale data xml files
     * conforming to localeElements.dtd and produces an xml file file
     * in the format
     */
    private static final int OPT_DIFF = 0x4000; /* 2exp15 */// PN
    private static final int OPT_DIFF_REF_COMMON = 0x8000; /* 2exp16 */// PN
    private static final int OPT_BULK = 0x00010000; // PN
    private static final int OPT_CASE_SENSITIVE = 0x00020000; // PN
    private static final int OPT_VETTING = 0x00040000;
    private static final int OPT_UNKNOWN = 0x00080000;

    private static final String COMMON = "common";
    private static final String ICU = "icu";
    private static final String IBM_TOR = "ibm";
    private static final String WINDOWS = "windows";
    private static final String SUNJDK = "sunjdk";
    private static final String IBMJDK = "ibmjdk";
    private static final String HPUX = "hp";
    private static final String APPLE = "apple";
    private static final String SOLARIS = "solaris";
    private static final String OPEN_OFFICE = "open_office";
    private static final String AIX = "aix";
    private static final String LINUX = "linux";

    private static final String ALTERNATE_TITLE = "(Original)";
    private static final String ALT_COLOR = "#DDDDFD";
    // PN added
    private static final String DIFF = "diff";
    private static final String DIFF_REF_COMMON = "diff_ref_common";
    private static final String BULK = "bulk";
    private static final String CASE_SENSITIVE = "case_sensitive";
    private static final String VETTING = "vetting";
    private static final String[] PLATFORM_PRINT_ORDER = {
        COMMON,
        ICU,
        WINDOWS,
        SUNJDK,
        IBMJDK,
        IBM_TOR,
        APPLE,
        SOLARIS,
        OPEN_OFFICE,
        AIX,
        LINUX,
        HPUX,
    };

    private static final String USER_OPTIONS[] = {
        "-" + COMMON,
        "-" + ICU,
        "-" + IBM_TOR,
        "-" + WINDOWS,
        "-" + SUNJDK,
        "-" + IBMJDK,
        "-" + HPUX,
        "-" + APPLE,
        "-" + SOLARIS,
        "-" + OPEN_OFFICE,
        "-" + AIX,
        "-" + LINUX,
        "-s",
        "-d",
        "-" + DIFF, // PN added, indicates that only differing elements/attributes to be written to html
        "-" + DIFF_REF_COMMON, // PN added, same as diff only common is excluded from diff but gets printed to html for
        // reference purposes
        "-" + BULK, // do a bulk comparison of folder contents
        "-" + CASE_SENSITIVE, // do case sensitive matching (by default it's not case sensitive)
        "-" + VETTING // go into Vetting mode. (show draft, etc)
    };

    public static void main(String[] args) {
        LDMLComparator comparator = new LDMLComparator();
        comparator.processArgs(args);
    }

    static Collator getDefaultCollation() {
        // if (DEFAULT_COLLATION != null) return DEFAULT_COLLATION;
        RuleBasedCollator temp = (RuleBasedCollator) Collator.getInstance(ULocale.ENGLISH);
        temp.setStrength(Collator.IDENTICAL);
        temp.setNumericCollation(true);
        // DEFAULT_COLLATION = temp;
        return temp;
    }

    Hashtable<String, String> optionTable = new Hashtable<String, String>();
    private String destFolder = ".";
    private String localeStr;
    private String ourCvsVersion = "";
    private Calendar cal = Calendar.getInstance();
    private Hashtable<String, String> colorHash = new Hashtable<String, String>();
    private String goldFileName;
    private String goldKey;
    private int serialNumber = 0;
    private Map<String, Object> compareMap = new TreeMap<String, Object>(getDefaultCollation());
    private Hashtable<String, String> doesNotExist = new Hashtable<String, String>();
    private Hashtable<String, String> requested = new Hashtable<String, String>();
    private Hashtable<String, String> deprecatedLanguageCodes = new Hashtable<String, String>();
    private Hashtable<String, String> deprecatedCountryCodes = new Hashtable<String, String>();
    private Set<String> vettingSet = new TreeSet<String>();
    private String encoding = "UTF-8"; // default encoding

    // PN added
    private Vector<String> m_PlatformVect = new Vector<String>(); // holds names of platforms
    private Vector<String> m_PlatformFolderVect = new Vector<String>(); // holds names of folders containing locale data
    // for each platform
    private int m_iOptions;
    private Map<String, AccumulatedResults> m_AccumulatedResultsMap = new TreeMap<String, AccumulatedResults>();
    private int m_iTotalConflictingElements = 0;
    private int m_iTotalNonConflictingElements = 0;
    private Map<String, SummaryData> m_LocaleSummaryDataMap = new TreeMap<String, SummaryData>(); // key = localename,
    // data = summary info
    private boolean m_Vetting = false;

    private int m_totalCount = 0;
    private int m_diffcount = 0;
    private String m_Messages = "";

    private class CompareElement {
        String node;
        String index;
        String parentNode;
        Hashtable<String, String> platformData = new Hashtable<String, String>();
        String referenceUrl;
    }

    // PN added
    // used for bulk comparisons
    // holds the locales where the element identified by node,index and parentNode conflict
    // for at least 2 of the platforms tested
    // holds the locales where the element identified by node,index and parentNode don't
    // for at all the platforms tested
    private class AccumulatedResults {
        String node;
        String index;
        String parentNode;
        Vector<String> localeVectDiff = new Vector<String>(); // holds locales where a conflict in data was found
        Vector<String> localeVectSame = new Vector<String>(); // holds locales where a no conflict in data was found
    }

    private class SummaryData {
        String m_szPlatforms;
        int m_iNumConflictingElements;
    }

    LDMLComparator() {
        // initialize the color hash
        colorHash.put(COMMON, "#AD989D");
        colorHash.put(ICU, "#CCFF00");
        colorHash.put(IBM_TOR, "#FF7777");
        colorHash.put(WINDOWS, "#98FB98");
        colorHash.put(SUNJDK, "#FF6633");
        colorHash.put(IBMJDK, "#CCFFFF");
        colorHash.put(HPUX, "#FFE4B5");
        colorHash.put(APPLE, "#FFBBBB");
        colorHash.put(SOLARIS, "#CC9966");
        colorHash.put(OPEN_OFFICE, "#FFFF33");
        colorHash.put(AIX, "#EB97FE");
        colorHash.put(LINUX, "#1191F1");
        // TODO - use deprecatedMap instead.
        // deprecatedLanguageCodes.put("sh", "what ever the new one is");
        deprecatedLanguageCodes.put("iw", "he");
        deprecatedLanguageCodes.put("in", "id");
        deprecatedLanguageCodes.put("ji", "yi");
        deprecatedLanguageCodes.put("jw", "jv"); // this does not even exist, JDK thinks jw is javanese!!

        // country codes
        deprecatedCountryCodes.put("TP", "TL");
        deprecatedCountryCodes.put("ZR", "CD");
    }

    private void processArgs(String[] args) {
        m_iOptions = identifyOptions(args);
        if ((args.length < 2) || ((m_iOptions & OPT_UNKNOWN) != 0)) {
            printUsage();
            return;
        }
        boolean warning[] = new boolean[1];
        warning[0] = false;
        Enumeration<String> en = optionTable.keys();

        try {
            // check for bulk operation
            if ((m_iOptions & OPT_BULK) != 0) {
                doBulkComparison();
            } else {
                localeStr = goldFileName.substring(goldFileName.lastIndexOf(File.separatorChar) + 1,
                    goldFileName.lastIndexOf('.'));

                String fileName = destFolder + File.separator + localeStr + ".html";
                m_totalCount = 0;
                m_diffcount = 0;
                if ((m_iOptions & OPT_VETTING) != 0) {
                    m_Vetting = true;
                    addVettable(goldFileName, goldKey);
                } else {
                    addToCompareMap(goldFileName, goldKey);
                }
                for (; en.hasMoreElements();) {
                    String key = (String) en.nextElement();
                    String compFile = (String) optionTable.get(key);
                    if ((m_iOptions & OPT_VETTING) != 0) {
                        addVettable(goldFileName, goldKey);
                    } else {
                        addToCompareMap(compFile, key);
                    }
                }
                if ((m_totalCount == 0) && m_Vetting) { // only optional for vetting.
                    // System.out.println("INFO:  no file created (nothing to write..) " + fileName);
                } else {
                    ourCvsVersion = "";
                    getCVSVersion();
                    OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(fileName), encoding);
                    System.out.println("INFO: Writing: " + fileName + "\t(" + m_totalCount + " items)");
                    PrintWriter writer = new PrintWriter(os);
                    printHTML(writer, localeStr);
                    {
                        ULocale ourLocale = new ULocale(localeStr);
                        String idxFileName = destFolder + File.separator + ourLocale.getDisplayLanguage() + "_"
                            + ourLocale.getDisplayCountry() + "_" + localeStr + ".idx";
                        OutputStreamWriter is = new OutputStreamWriter(new FileOutputStream(idxFileName), "utf-8");
                        PrintWriter indexwriter = new PrintWriter(is);
                        indexwriter.println("<tr>");
                        indexwriter.println(" <td>" +
                            localeStr +
                            "</td>");
                        indexwriter.println(" <td><a href=\"" + localeStr + ".html" + "\">" +
                            ourLocale.getDisplayName() + "</a></td>");
                        indexwriter.println(" <td>" + m_totalCount + "</td>");
                        if (!m_Vetting) {
                            indexwriter.println(" <td>" + m_diffcount + "</td>");
                        }
                        indexwriter.println(" <td>" + LDMLUtilities.getCVSLink(localeStr, ourCvsVersion)
                            + ourCvsVersion + "</a></td>");
                        indexwriter.println("</tr>");
                        is.close();
                    }
                    // TODO: handle vettingSet;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void printUsage() {
        System.err.println("Usage: LDMLComparator [<option>:<gold>] filename1 [option] filename2 ... \n" +
            " LDMLComparator [-common:<gold>] filename [-icu] filename" +
            " [-ibmjdk] filename [-windows] filename" +
            " [-hpux]  filename [-solaris] filename" +
            " [-ibmtor] filename [-apple] filename" +
            " [-sunjdk]  filename [-open_office] filename" +
            " [-aix] filename [-linux] filename" +
            " [-diff / -diff_ref_common] [-bulk]" +
            " [-case_sensitive (only active if -diff of -diff-ref-common option selected)]");
        System.err.println("\nExample 1: \n " +
            "LDMLComparator -solaris:gold foldername1 -sunjdk foldername2 -common foldername3 -diff-ref-common -bulk\n"
            +
            "\t\t will do a bulk comparison of the locales in folder1 and folder2 \n " +
            "\t\t and print the values of any differing elements plus the \n" +
            "\t\t corresponding element's value in folder3 to bulk.html \n" +
            "\t\t as well as a summary to bulk_summary.html \n");
        System.err.println("Example 2: \n" +
            "LDMLComparator -common:gold filename1 -sunjdk filename2 -diff \n" +
            "\t\t will do a comparison of the locales specified by filename1 and \n" +
            "\t\t filename2 and print the values of any differing elements \n" +
            "\t\t to a file called filename1.html in the current directory \n");
    }

    private int identifyOptions(String[] options) {
        int result = 0;
        for (int j = 0; j < options.length; j++) {
            String option = options[j];
            boolean isGold = false;
            if (option.startsWith("-")) {
                if (option.indexOf(":gold") > 0) {
                    option = option.substring(0, option.indexOf(":"));
                    isGold = true;
                }
                boolean optionRecognized = false;
                for (int i = 0; i < USER_OPTIONS.length; i++) {

                    if (USER_OPTIONS[i].equals(option)) {
                        result |= (int) (1 << i); // calculate option bit value
                        optionRecognized = true;
                        if (USER_OPTIONS[i].equals("-s")) {
                        } else if (USER_OPTIONS[i].equals("-d")) {
                            destFolder = options[++j];
                        } else if (USER_OPTIONS[i].equals("-" + DIFF)) {

                        } else if (USER_OPTIONS[i].equals("-" + DIFF_REF_COMMON)) {

                        } else if (USER_OPTIONS[i].equals("-" + BULK)) {

                        } else if (USER_OPTIONS[i].equals("-" + VETTING)) {
                            m_Vetting = true;
                        } else if (USER_OPTIONS[i].equals("-" + CASE_SENSITIVE)) {
                        } else {
                            if (!isGold) {
                                optionTable.put(option.substring(1, option.length()), options[++j]);

                            } else {
                                goldFileName = options[++j];
                                goldKey = option.substring(1, option.length());
                            }
                            // PN added
                            m_PlatformVect.add(option.substring(1, option.length()));
                            m_PlatformFolderVect.add(options[j]);
                        }
                        break;
                    }
                }
                if (!optionRecognized) {
                    result |= OPT_UNKNOWN;
                }
            } else {
                if (m_Vetting == true) {
                    vettingSet.add(option);
                }
            }
        }

        return result;
    }

    private void printTableHeader(PrintWriter writer) {

        writer.print("            <tr>\n" +
            "                <th>N.</th>\n" +
            "                <th>ParentNode</th>\n" +
            "                <th>Name</th>\n" +
            "                <th>ID</th>\n");

        for (int i = 0; i < PLATFORM_PRINT_ORDER.length && PLATFORM_PRINT_ORDER[i] != null; i++) {
            String name = PLATFORM_PRINT_ORDER[i];
            String folder;

            Object obj = requested.get(name);
            if (obj != null && doesNotExist.get(name) == null) {
                folder = name + "/main/";
                if (name.equals("icu") || name.equals("common") || name.indexOf("jdk") >= 0) {
                    int index = localeStr.indexOf("_");
                    String parent = "";
                    if (index > -1) {
                        parent = localeStr.substring(0, index);
                    }
                    writer.print("                <th bgcolor=\"" +
                        (String) colorHash.get(name) + "\">" +
                        name.toUpperCase() +
                        " (<a href=\"../../" + folder + localeStr + ".xml\">" + localeStr + "</a>," +
                        " <a href=\"../../" + folder + parent + ".xml\">" + parent + "</a>," +
                        " <a href=\"../../" + folder + "root.xml\">root</a>)" +
                        "</th>\n");
                } else {
                    writer.print("                <th bgcolor=\"" +
                        (String) colorHash.get(name) + "\">" +
                        name.toUpperCase() +
                        " (<a href=\"../../" + folder + localeStr + ".xml\">" + localeStr + "</a>)" +
                        "</th>\n");
                }
            }
        }
        if (m_Vetting) {
            writer.print("<th bgcolor=\"" + ALT_COLOR + "\">" + ALTERNATE_TITLE + "</th>");
        }
        writer.print("            </tr>\n");
    }

    // PN added
    private void printTableHeaderForDifferences(PrintWriter writer) {

        writer.print("            <tr>\n" +
            "                <th width=10%>N.</th>\n" +
            "                <th width=10%>ParentNode</th>\n" +
            "                <th width=10%>Name</th>\n" +
            "                <th width=10%>ID</th>\n");

        for (int i = 0; i < m_PlatformVect.size(); i++) {
            String name = (String) m_PlatformVect.elementAt(i);
            String folder;

            // Object obj = requested.get(name);
            // if(obj!=null && doesNotExist.get(name)==null )
            // {
            folder = name + "/xml/";
            writer.print("                <th bgcolor=\"" +
                (String) colorHash.get(name) + "\">" +
                name.toUpperCase() +
                " (<a href=\"../" + folder + localeStr + ".xml\">xml</a>)" +
                "</th>\n");
            // not used numPlatforms++;

            // }
        }
        if (m_Vetting) {
            writer.print("<th>" + ALTERNATE_TITLE + "</th>");
        }
        writer.print("            </tr>\n");
    }

    // PN added
    // method to print differing elements/attributes only to HTML
    // returns false if a difference found otherwise true
    private boolean printDifferentValues(CompareElement element, PrintWriter writer) {
        boolean isEqual = true;
        // following don't count
        if ((element.node.compareTo((String) "generation") == 0)
            || (element.node.compareTo((String) "version") == 0)) {
            return isEqual;
        }

        String compareTo = null;
        boolean bFoundFirst = false;
        for (int i = 0; i < m_PlatformVect.size(); i++) {
            String value = element.platformData.get(m_PlatformVect.elementAt(i));
            if (value == null)
                continue;
            // loop until non null value is found, this is the reference for comparison
            if (bFoundFirst == false) {
                compareTo = value;
                bFoundFirst = true;
            } else { // we have something to compare this element to
                if (Normalizer.compare(compareTo, value, 0) == 0) {
                    isEqual = true;
                } else if (Normalizer.compare(compareTo, value, Normalizer.COMPARE_IGNORE_CASE) == 0) {
                    if ((m_iOptions & OPT_CASE_SENSITIVE) == 0) { // it's not a case sensitive search so this is a match
                        isEqual = true;
                    } else {
                        isEqual = false;
                        break; // we have found a difference therefore break out of loop , we will print full row
                    }
                } else {
                    isEqual = false;
                    break; // we have found a difference therefore break out of loop , we will print full row
                }
            } // end if
        } // end while

        // if any differences found then print all non null values
        if (isEqual == false) {
            writer.print("            <tr>\n");
            writer.print("                <td><a NAME=\"" + serialNumber + "\" href=\"#" + serialNumber + "\">"
                + serialNumber + "</a></td>\n");
            writer.print("                <td>" + mapToAbbr(element.parentNode) + "</td>\n");
            writer.print("                <td>" + mapToAbbr(element.node) + "</td>\n");
            writer.print("                <td>" + element.index + "</td>\n");

            for (int i = 0; i < m_PlatformVect.size(); i++) {
                String val = (String) element.platformData.get(m_PlatformVect.elementAt(i));
                if (val != null) {
                    writer.print("                <td>" + val + "</td>\n");
                } else {
                    writer.print("                <td>&nbsp;</td>\n");
                }
            } // end while

            writer.print("            </tr>\n");
            serialNumber++;
        } // endif
        return isEqual;
    }

    // PN added
    // method to print differing elements/attributes only to HTML excluding Common from diff
    // only if the other platfroms differ amongst themselves will the Common data be printed
    // returns false if a difference found otherwise true
    private boolean printDifferentValuesWithRef(CompareElement element, PrintWriter writer) {
        boolean isEqual = true;
        // following don't count
        if ((element.node.compareTo((String) "generation") == 0)
            || (element.node.compareTo((String) "version") == 0)) {
            return isEqual;
        }

        String compareTo = null;
        boolean bFoundFirst = false;
        for (int i = 0; i < m_PlatformVect.size(); i++) {
            // excluding Common from diff
            String platform = (String) m_PlatformVect.elementAt(i);
            if (platform.compareTo(COMMON) == 0)
                continue;

            String value = (String) element.platformData.get(platform);
            if (value == null)
                continue;

            // loop until non null value is found, this is the reference for comparison
            if (bFoundFirst == false) {
                compareTo = value;
                bFoundFirst = true;
            } else { // we have something to compare this element to
                if (Normalizer.compare(compareTo, value, 0) == 0) {
                    isEqual = true;
                } else if (Normalizer.compare(compareTo, value, Normalizer.COMPARE_IGNORE_CASE) == 0) {
                    // case difference on date and time format doesn't matter
                    if ((element.parentNode.compareTo("timeFormat") == 0)
                        || (element.parentNode.compareTo("dateFormat") == 0)) {
                        isEqual = true;
                    } else {
                        if ((m_iOptions & OPT_CASE_SENSITIVE) == 0) { // it's not a case sensitive search so this is a match
                            isEqual = true;
                        } else {
                            isEqual = false;
                            break; // we have found a difference therefore break out of loop , we will print full row
                        }
                    }
                } else {
                    isEqual = false;
                    break; // we have found a difference therefore break out of loop , we will print full row
                }
            } // end if
        } // end while

        // if any differences found then print all non null values
        if (isEqual == false) {
            writer.print("            <tr>\n");
            writer.print("                <td><a NAME=\"" + serialNumber + "\" href=\"#" + serialNumber + "\">"
                + serialNumber + "</a></td>\n");
            writer.print("                <td>" + mapToAbbr(element.parentNode) + "</td>\n");
            writer.print("                <td>" + mapToAbbr(element.node) + "</td>\n");
            writer.print("                <td>" + element.index + "</td>\n");

            for (int i = 0; i < m_PlatformVect.size(); i++) {
                String val = (String) element.platformData.get(m_PlatformVect.elementAt(i));
                if (val != null) {
                    writer.print("                <td>" + val + "</td>\n");
                } else {
                    writer.print("                <td>&nbsp;</td>\n");
                }
            } // end while

            writer.print("            </tr>\n");
            serialNumber++;
        } // endif

        return isEqual;
    }

    private void printValue(CompareElement element, PrintWriter writer) {

        writer.print("            <tr>\n");
        writer.print("                <td><a NAME=\"" + serialNumber + "\" href=\"#" + serialNumber + "\">"
            + serialNumber + "</a></td>\n");
        writer.print("                <td>" + mapToAbbr(element.parentNode) + "</td>\n");
        writer.print("                <td>" + mapToAbbr(element.node) + "</td>\n");
        writer.print("                <td>" + element.index + "</td>\n");
        serialNumber++;

        for (int i = 0; i < PLATFORM_PRINT_ORDER.length; i++) {
            String value = (String) element.platformData.get(PLATFORM_PRINT_ORDER[i]);
            String color = (String) colorHash.get(PLATFORM_PRINT_ORDER[i]);
            boolean caseDiff = false;
            boolean isEqual = false;
            // the locale exists for the given platform but there is no data
            // so just write non breaking space and continue
            // else the object contains value to be written .. so write it
            if (value == null) {
                if (requested.get(PLATFORM_PRINT_ORDER[i]) != null && doesNotExist.get(PLATFORM_PRINT_ORDER[i]) == null) {
                    writer.print("                <td>&nbsp;</td>\n");
                }
            } else {
                // pick the correct color
                for (int j = 0; j < i; j++) {
                    String compareTo = (String) element.platformData.get(PLATFORM_PRINT_ORDER[j]);
                    if (compareTo == null) {
                        continue;
                    } else if (value.equals("")) {
                        color = "#FFFFFF";
                        break;
                    } else if (element.parentNode.indexOf("decimalFormat") > -1
                        || element.parentNode.indexOf("currencyFormat") > -1) {
                        if (comparePatterns(compareTo, value)) {
                            color = (String) colorHash.get(PLATFORM_PRINT_ORDER[j]);
                            isEqual = true;
                            break;
                        }
                    } else if (Normalizer.compare(compareTo, value, 0) == 0) {
                        color = (String) colorHash.get(PLATFORM_PRINT_ORDER[j]);
                        isEqual = true;
                        break;
                    } else if (Normalizer.compare(compareTo, value, Normalizer.COMPARE_IGNORE_CASE) == 0) {
                        caseDiff = true;
                        color = (String) colorHash.get(PLATFORM_PRINT_ORDER[j]);
                        break;
                    }
                }
                if (isEqual) {
                    value = "=";
                } else {
                    if (i > 0) { // not the first platform
                        if ((element.node.compareTo((String) "generation") == 0)
                            || (element.node.compareTo((String) "version") == 0)) {
                            // ignored
                        } else {
                            m_diffcount++;
                        }
                    }
                }
                if (m_Vetting) {
                    String altText = (String) element.platformData.get("ALT");
                    writer.print("<td>" + value);
                    String parName = mapToAbbr(element.parentNode);
                    if ((parName.indexOf("_dateFormat") != -1)
                        || (parName.indexOf("_timeFormat") != -1)) {
                        writer.print("<form method=\"POST\" action=\"http://oss.software.ibm.com/cgi-bin/icu/lx/\">" +
                            "<input type=hidden name=\"_\" value=\"" + localeStr + "\"/>" +
                            "<input type=hidden name=\"x\" value=\"" + "dat" + "\"/>" +
                            "<input type=hidden name=\"str\" value=\"" + value + "\"/>" +
                            "<input type=submit value=\"" + "Test" + "\"/>" +
                            "</form>");
                    }
                    if (/* m_Vetting && */element.referenceUrl != null) {
                        writer.print("<br><div align='right'><a href=\"" + element.referenceUrl
                            + "\"><i>(Ref)</i></a></div>");
                    }
                    writer.print("</td>");
                    if (altText != null) {
                        writer.print("        <td bgcolor=" + ALT_COLOR + ">" + altText);
                        writer.print("</td>\n");
                    }
                } else {
                    if (caseDiff == true) {
                        writer.print("                <td bgcolor=" + color + ">" + value + "&#x2020;");
                    } else {
                        writer.print("                <td bgcolor=" + color + ">" + value);
                    }
                    writer.print("</td>\n");
                }
            }
        }
        writer.print("            </tr>\n");
    }

    private String mapToAbbr(String source) {
        if (source.equals("icu:ruleBasedNumberFormat")) {
            return "icu:rbnf";
        }
        if (source.equals("icu:ruleBasedNumberFormats")) {
            return "icu:rbnfs";
        }
        if (source.equals("exemplarCharacters")) {
            return "exemplarC";
        }
        if (source.equals("localizedPatternChars")) {
            return "lpc";
        }
        return source;
    }

    private void printHTML(PrintWriter writer, String localeStr) {
        // System.out.println("INFO: Creating the comparison chart ");
        ULocale locale = new ULocale(localeStr);
        String displayName = localeStr + " (" + locale.getDisplayName() + ") ";
        if ((m_iOptions & OPT_DIFF_REF_COMMON) != 0)
            writer.print("<p>   Common data shown for reference purposes only</p>\n");

        if (!m_Vetting) {
            writer.print("<html>\n" +
                "    <head>\n" +
                "        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n" +
                "        <title>" + localeStr + "</title>\n" +
                "    </head>\n" +
                "    <style>\n" +
                "         <!--\n" +
                "         table        { border-spacing: 0; border-collapse: collapse; width: 100%; \n" +
                "                        border: 1px solid black }\n" +
                "         td, th       { width: 10%; border-spacing: 0; border-collapse: collapse; color: black; \n" +
                "                        vertical-align: top; border: 1px solid black }\n" +
                "         -->\n" +
                "     </style>" +
                "     <body bgcolor=\"#FFFFFF\">\n" +
                "        <p><b>" + displayName +
                "<a href=\"http://oss.software.ibm.com/cgi-bin/icu/lx/en/?_=" + localeStr + "\">Demo</a>, " +
                "<a href=\"../../comparison_charts.html\">Cover Page</a>, " +
                "<a href=\"./index.html\">Index</a>, " +
                "<a href=\"../collation/" + localeStr + ".html\">Collation</a> " +
                "</b></p>\n" +
                "        <table>\n");
        } else {
            writer.print("<html>\n" +
                "    <head>\n" +
                "        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n" +
                "        <title>Draft/Alt: " + localeStr + "</title>\n" +
                "    </head>\n" +
                "    <style>\n" +
                "         <!--\n" +
                "         table        { border-spacing: 0; border-collapse: collapse;  \n" +
                "                        border: 1px solid black }\n" +
                "         td, th       { border-spacing: 0; border-collapse: collapse;  color: black; \n" +
                "                        vertical-align: top; border: 1px solid black }\n" +
                "         -->\n" +
                "     </style>" +
                "     <body bgcolor=\"#FFFFFF\">\n" +
                "        <p><b>" + displayName +
                "<a href=\"http://oss.software.ibm.com/cgi-bin/icu/lx/en/?_=" + localeStr + "\">Demo</a>, " +
                "<a href=\"./index.html\">Main and About</a>, " +
                "</b></p>\n");
            if ((ourCvsVersion != null) && (ourCvsVersion.length() > 0)) {
                writer.println("<h3><tt>" + LDMLUtilities.getCVSLink(localeStr) + localeStr + ".xml</a> version " +
                    LDMLUtilities.getCVSLink(localeStr, ourCvsVersion) + ourCvsVersion + "</a></tt></h3>");
            }
            writer.print("        <table>\n");
        }

        // PN added
        if (((m_iOptions & OPT_DIFF) != 0)
            || ((m_iOptions & OPT_DIFF_REF_COMMON) != 0)) {
            printTableHeaderForDifferences(writer);
        } else {
            printTableHeader(writer);
        }

        // walk down the compare map and print the data
        for (Object obj : compareMap.keySet()) {
            CompareElement element;
            if (obj != null) {
                Object value = compareMap.get(obj);
                if (value instanceof CompareElement) {
                    element = (CompareElement) value;
                } else {
                    throw new RuntimeException(
                        "The object stored in the compare map is not an instance of CompareElement");
                }
                // PN added
                if ((m_iOptions & OPT_DIFF) != 0) {
                    printDifferentValues(element, writer); // only print differences
                } else if ((m_iOptions & OPT_DIFF_REF_COMMON) != 0) {
                    printDifferentValuesWithRef(element, writer);
                } else {
                    printValue(element, writer);
                }
            } else {
                throw new RuntimeException("No objects stored in the compare map!");
            }

        }
        writer.print("        </table>\n");

        if (m_Vetting) {
            if (m_Messages.length() > 0) {
                writer.print("<table bgcolor=\"#FFBBBB\" border=3><tr><th>Warnings (please see source LDML)</th></tr>" +
                    "<tr><td>" + m_Messages + "</td></tr></table><p/><p/>\n");
            }
            writer.print("<i>Interim page - subject to change</i> (<a href=\"./index.html\">Help</a>)<br/>");
        }

        writer.print("        <p>Created on: " + cal.getTime() + "</p>\n" +
            "    </body>\n" +
            "</html>\n");
        writer.flush();

        writer.flush();
    }

    private Document getFullyResolvedLocale(String localeName, String fileName) {
        // here we assume that "_" is the delimiter
        int index = fileName.lastIndexOf(File.separatorChar);
        String sourceDir = fileName.substring(0, index + 1);
        String locale = fileName.substring(index + 1, fileName.lastIndexOf("."));
        System.out.println("INFO: Creating fully resolved tree for : " + fileName);

        Document doc = LDMLUtilities.getFullyResolvedLDML(sourceDir, locale, true, true, true, true);
        /*
         * debugging code
         *
         * try{
         * OutputStreamWriter writer = new OutputStreamWriter(new
         * FileOutputStream(destFolder+File.separator+localeName+"_debug.xml"),encoding);
         * LDMLUtilities.printDOMTree(doc,new PrintWriter(writer));
         * writer.flush();
         * }catch( Exception e){
         * //throw the exception away .. this is for debugging
         * }
         */
        return doc;
    }

    private boolean addToCompareMap(String fileName, String key) {
        // parse the test doc only if gold doc was parsed OK
        Document testDoc = getFullyResolvedLocale(key, fileName);
        requested.put(key, "");
        if (null == testDoc) {
            doesNotExist.put(key, "");
            return false;
        }
        return extractMergeData(testDoc, key, false);

    }

    private Document getParsedLocale(String localeName, String fileName) {
        // here we assume that "_" is the delimiter
        System.out.println("INFO: Parsing " + fileName);
        Document doc = LDMLUtilities.parse(fileName, true); // ?

        return doc;
    }

    private boolean addVettable(String fileName, String key) {
        // parse the test doc only if gold doc was parsed OK
        Document testDoc = getParsedLocale(key, fileName);
        requested.put(key, "");
        if (null == testDoc) {
            doesNotExist.put(key, "");
            return false;
        }
        return extractMergeData(testDoc, key, false);

    }

    private boolean comparePatterns(String pat1, String pat2) {
        // TODO: just return for now .. this is useful only
        // when comparing data from toronto
        try {
            double args1 = 10000000000.00;
            double args2 = -10000000000.00;

            DecimalFormat fmt = new DecimalFormat();

            fmt.applyPattern(pat1);
            String s1 = fmt.format(args1);
            String s3 = fmt.format(args2);
            fmt.applyPattern(pat2);
            String s2 = fmt.format(args1);
            String s4 = fmt.format(args2);
            if (s1.equals(s2) && s3.equals(s4)) {
                return true;
            }
        } catch (Exception e) {
            // throw away the exception
        }
        return false;

        // return true;
    }

    private String trim(String source) {
        char[] src = source.toCharArray();
        char[] dest = new char[src.length];

        int start = 0;
        while (start < (src.length) && (UCharacter.isWhitespace(src[start]))) {
            start++;
        }
        int stop = src.length - 1;
        while (stop > 0 && (UCharacter.isWhitespace(src[stop]) || (src[stop] == 0xA0))) {
            stop--;
        }
        if (stop != -1 && start != src.length) {
            System.arraycopy(src, start, dest, 0, (stop - start) + 1);
            return new String(dest, 0, (stop - start) + 1);
        } else {
            return new String();
        }

    }

    private final void addElement(String childNode, String parentNode, String id, String index,
        String platformValue, String platformName) {
        addElement(childNode, parentNode, id, index, platformValue, platformName, null);
    }

    private void addElement(String childNode, String parentNode, String id, String index,
        String platformValue, String platformName, String referenceUrl) {
        m_totalCount++;
        Object obj = compareMap.get(id);
        CompareElement element;
        if (obj == null) {
            element = new CompareElement();
            // initialize the values
            element.index = index;
            element.parentNode = parentNode;
            element.node = childNode;
            element.referenceUrl = referenceUrl;
            // add the element to the compare map
            compareMap.put(id, element);
        } else {
            if (obj instanceof CompareElement) {
                element = (CompareElement) obj;
            } else {
                throw new RuntimeException("The object stored in the compareMap is not a CompareElement object!");
            }
        }

        if ((!element.index.equals(index)) ||
            (!element.node.equals(childNode)) ||
            (!element.parentNode.equals(parentNode))) {
            throw new RuntimeException("The retrieved object is not the same as the one trying to be saved, id is "
                + id);
        }

        element.platformData.put(platformName, platformValue);
    }

    private boolean childrenAreElements(Node node) {
        NodeList list = node.getChildNodes();
        for (int i = 0; i < list.getLength(); i++) {
            if (list.item(i).getNodeType() == Node.ELEMENT_NODE) {
                return true;
            }
        }
        return false;
    }

    private String getTag(String childNodeName, String index) {

        // for months make index a,b,c,d etc
        if (childNodeName.indexOf("month") > -1) {
            int i = Integer.parseInt(index);
            StringBuffer temp = new StringBuffer();
            temp.append((char) ('a' + i));
            return temp.toString();
        } else if (childNodeName.indexOf("day") > -1) {
            if (index.equals("sun")) {
                return "a";
            } else if (index.equals("mon")) {
                return "b";
            } else if (index.equals("tue")) {
                return "c";
            } else if (index.equals("wed")) {
                return "d";
            } else if (index.equals("thu")) {
                return "e";
            } else if (index.equals("fri")) {
                return "f";
            } else if (index.equals("sat")) {
                return "g";
            }
        } else {
            return index;
        }
        return "";
    }

    private boolean extractMergeData(Node node, String key, boolean parentDraft) {
        Node childOfSource;
        for (childOfSource = node.getFirstChild(); childOfSource != null; childOfSource = childOfSource
            .getNextSibling()) {
            if (childOfSource.getNodeType() != Node.ELEMENT_NODE) {
                continue;
            }
            String altText = null;
            // String altReferenceUrl = null;
            Node altForChild = null;
            boolean subDraft = parentDraft;
            String childOfSourceName = childOfSource.getNodeName();
            // Ignore collation and special tags
            if (childOfSourceName.equals("collations") || childOfSource.equals("special")
                || childOfSourceName.indexOf(":") > -1) {
                continue;
            }

            if (m_Vetting && LDMLUtilities.isNodeDraft(childOfSource)) {
                if (!subDraft) {
                    subDraft = true;
                }
            }
            String referenceUrl = null;
            if (m_Vetting) {
                referenceUrl = LDMLUtilities.getAttributeValue(childOfSource, LDMLConstants.REFERENCES);
                if ((referenceUrl != null) && (referenceUrl.length() == 0)) {
                    referenceUrl = null;
                }
            }

            if (m_Vetting) { /* Should this be always checked? */
                String alt = LDMLUtilities.getAttributeValue(childOfSource, LDMLConstants.ALT);
                if (alt != null) {
                    if (alt.equals(LDMLConstants.PROPOSED)) {
                        if (subDraft == false) {
                            throw new IllegalArgumentException("***** ERROR Proposed but not draft? "
                                + childOfSource.toString());
                            // NOTREACHED
                        }
                        altForChild = LDMLUtilities.getNonAltNodeLike(node, childOfSource);
                        if (altForChild == null) {
                            System.out.println("WARNING: can't find a node like this one: " + childOfSource.toString()
                                + " - consider removing the alt=\"proposed\" attribute.");
                            alt = null;
                        }
                        // altReferenceUrl = LDMLUtilities.getAttributeValue(altForChild, LDMLConstants.REFERENCES);
                        // if((altReferenceUrl!=null)&&(altReferenceUrl.length()==0)) {
                        // altReferenceUrl = null;
                        // }
                    } else if (subDraft) { /* don't care about nondraft */
                        String type = LDMLUtilities.getAttributeValue(childOfSource, LDMLConstants.TYPE);
                        if (type == null) {
                            type = "";
                        }
                        m_Messages = m_Messages + " <br> UNKNOWN alt type '" + alt + "' for " +
                            node.getNodeName() + "/" + childOfSourceName + "/" + type;
                        System.err.println("Warning: unknown alt type '" + alt + "'  - *IGNORING*. "
                            + childOfSource.toString());
                        continue;
                    }
                }
            }

            if (childrenAreElements(childOfSource) == false) {
                NamedNodeMap attr = childOfSource.getAttributes();
                Node typeNode = attr.getNamedItem("type");
                Node altNode = attr.getNamedItem("alt");
                String index = "";
                if (typeNode != null) {
                    /*
                     * if(childOfSource.getNodeName().equals("era")&&!key.equals("common")){
                     * //remap type for comparison purpose
                     * // TODO remove this hack
                     * int j = Integer.parseInt(typeNode.getNodeValue());
                     * if(j>0){
                     * j--;
                     * }
                     * typeNode.setNodeValue(Integer.toString(j));
                     * }
                     */
                    String temp = typeNode.getNodeValue();

                    if (!temp.equals("standard")) {
                        index = temp;
                    }

                }
                String alt = null;
                if (altNode != null) {
                    alt = altNode.getNodeValue();
                }
                if (m_Vetting) { // TODO: all?
                    Node keyNode = attr.getNamedItem("key");
                    if (keyNode != null) {
                        String temp = keyNode.getNodeValue();
                        index = index + " (" + temp + ")";
                    }
                }
                String nodeValue = "";
                Node valueNode = childOfSource.getFirstChild();
                if (valueNode != null) {
                    String temp = trim(valueNode.getNodeValue());
                    if (!temp.equals("standard")) {
                        nodeValue = temp;
                    }
                }
                if (altForChild != null) {
                    Node valueNode2 = altForChild.getFirstChild();
                    if (valueNode2 != null) {
                        String temp = trim(valueNode2.getNodeValue());
                        if (!temp.equals("standard")) {
                            altText = temp;
                        } else {
                            altText = "??? alt=standard";
                        }
                    } else {
                        altText = "??? alt has no value";
                    }
                }
                Node parentNode = childOfSource.getParentNode();
                String parentNodeName = trim(parentNode.getNodeName());
                String childNodeName = trim(childOfSource.getNodeName());
                Node grandParentNode = childOfSource.getParentNode().getParentNode();
                String grandParentNodeName = grandParentNode.getNodeName();
                NamedNodeMap parentAttrib = parentNode.getAttributes();
                String type = "";
                if (parentAttrib != null) {
                    Node mytypeNode = parentAttrib.getNamedItem("type");
                    if (mytypeNode != null) {
                        String mytype = mytypeNode.getNodeValue();
                        if (!mytype.equals("standard")) {
                            if (!parentNodeName.equals("calendar")) {
                                type = mytype;
                            } else {
                                parentNodeName = mytype;
                            }
                        }
                    }

                }
                if (grandParentNodeName.equals("eras")) {
                    Node calendar = grandParentNode.getParentNode();
                    NamedNodeMap gpa = calendar.getAttributes();
                    Node gptNode = gpa.getNamedItem("type");
                    if (gptNode != null) {
                        String gptType = gptNode.getNodeValue();
                        if (!gptType.equals("standard")) {
                            grandParentNodeName = gptType;
                        }
                    }
                    parentNodeName = grandParentNodeName + "\u200b_" + parentNodeName;
                }
                if (grandParentNodeName.equals("calendar")) {
                    NamedNodeMap gpa = grandParentNode.getAttributes();
                    Node gptNode = gpa.getNamedItem("type");
                    if (gptNode != null) {
                        String gptType = gptNode.getNodeValue();
                        if (!gptType.equals("standard")) {
                            grandParentNodeName = gptType;
                        }
                    }
                    parentNodeName = grandParentNodeName + "\u200b_" + parentNodeName;
                }
                if (grandParentNodeName.equals("monthContext") || grandParentNodeName.equals("dayContext") ||
                    grandParentNodeName.equals("dateFormatLength") || grandParentNodeName.equals("timeFormatLength") ||
                    grandParentNodeName.equals("dateTimeFormatLength")) {

                    Node calendar = grandParentNode.getParentNode().getParentNode();
                    NamedNodeMap ggpa = calendar.getAttributes();
                    Node ggptNode = ggpa.getNamedItem("type");
                    if (ggptNode != null) {
                        String ggptType = ggptNode.getNodeValue();
                        if (!ggptType.equals("standard")) {
                            grandParentNodeName = ggptType;
                            parentNodeName = ggptType + "\u200b_" + parentNodeName;
                        }
                    }
                    NamedNodeMap gpa = grandParentNode.getAttributes();
                    Node gptNode = gpa.getNamedItem("type");
                    if (gptNode != null) {
                        String gptType = gptNode.getNodeValue();
                        if (!gptType.equals("standard")) {
                            parentNodeName = parentNodeName + "\u200b_" + gptType;
                        }
                    }
                    NamedNodeMap pa = parentNode.getAttributes();
                    Node ptNode = pa.getNamedItem("type");
                    if (ptNode != null) {
                        String ptType = ptNode.getNodeValue();
                        if (!ptType.equals("standard")) {
                            parentNodeName = parentNodeName + "\u200b_" + ptType;
                        }
                    }

                }
                if (childNodeName.equals("pattern") || grandParentNodeName.equals("zone")) {
                    if (parentNodeName.indexOf("date") == -1 && parentNodeName.indexOf("time") == -1) {
                        NamedNodeMap at = grandParentNode.getAttributes();
                        Node mytypeNode = at.getNamedItem("type");
                        if (mytypeNode != null) {
                            String mytype = mytypeNode.getNodeValue();
                            if (!mytype.equals("standard")) {
                                if (type.equals("")) {
                                    type = mytype;
                                } else {
                                    type = type + "\u200b_" + mytype;
                                }

                            }
                        }
                    }
                }
                if (grandParentNodeName.equals("special") || parentNodeName.equals("special")
                    || childNodeName.equals("special")
                    || grandParentNodeName.indexOf(":") > 0) {
                    continue;
                }
                if (!nodeValue.equals("") &&
                    !childOfSource.getNodeName().equals("version")) {

                    // for country codes and language codes
                    // replace the deprecated codes with the latest ones
                    if (childNodeName.equals("language")) {
                        String temp = (String) deprecatedLanguageCodes.get(index);
                        if (temp != null) {
                            index = temp;
                        }
                    } else if (childNodeName.equals("territory")) {
                        String temp = (String) deprecatedCountryCodes.get(index);
                        if (temp != null) {
                            index = temp;
                        }
                        if (index != null && alt != null) {
                            index = index + "_" + alt;
                        }
                    }
                    String id = "";
                    if (!type.equals("")) {
                        id = parentNodeName + "_" + childNodeName + "_" + type + "_" + getTag(childNodeName, index)
                            + "_" + grandParentNodeName;
                    } else {
                        id = parentNodeName + "_" + childNodeName + "_" + getTag(childNodeName, index) + "_"
                            + grandParentNodeName;
                    }
                    if (!index.equals("")) {
                        if (!index.equals(nodeValue) && !index.equals("Fallback")) {
                            if (!m_Vetting || subDraft) {
                                addElement(childNodeName, parentNodeName, id, index, nodeValue, key, referenceUrl);
                                if (altText != null) {
                                    addElement(childNodeName, parentNodeName, id, index, altText, "ALT", null /* altReferenceUrl */);
                                }
                            }
                        }
                    } else {
                        if (!type.equals(nodeValue) && !type.equals("Fallback")) {
                            if (!m_Vetting || subDraft) {
                                addElement(childNodeName, parentNodeName, id, type, nodeValue, key, referenceUrl);
                                if (altText != null) {
                                    addElement(childNodeName, parentNodeName, id, index, altText, "ALT", null /* altReferenceUrl */);
                                }
                            }
                        }
                    }
                }
                if (attr.getLength() > 0 && typeNode == null) { // TODO: make this a fcn
                    // add an element for each attribute different for each attribute
                    if (!m_Vetting || subDraft) {
                        for (int i = 0; i < attr.getLength(); i++) {
                            Node item = attr.item(i);
                            String attrName = item.getNodeName();
                            if (attrName.equals("type")) {
                                continue;
                            }
                            if (attrName.equals("alt")) {
                                continue;
                            }
                            if (attrName.equals("draft")) {
                                continue;
                            }
                            if (grandParentNodeName.equals("zone")) {
                                parentNodeName = grandParentNodeName + "\u200b_" + parentNodeName;
                            }
                            String id = grandParentNodeName + "_" + parentNodeName + "_" + childNodeName + "_" + type
                                + "_" + attrName;
                            String subNodeValue = item.getNodeValue();
                            if (altForChild != null) {
                                System.err.println(parentNodeName + "/" + childNodeName + " alt?? : " + altText);
                                throw new IllegalArgumentException("UNKNOWN ALT SUBTAG + " + parentNodeName + "/"
                                    + childNodeName + " alt?? : " + altText + " not " + subNodeValue);
                            }
                            if (!index.equals("")) {
                                addElement(childNodeName, parentNodeName, id, index, subNodeValue, key);
                            } else if (!type.equals("")) {
                                addElement(childNodeName, parentNodeName, id, type, subNodeValue, key);
                            } else {
                                if (!attrName.equals("draft")) {
                                    addElement(childNodeName, parentNodeName, id, attrName, subNodeValue, key);
                                }
                            }
                        }
                    }
                }
            } else {
                // the element has more children .. recurse to pick them all
                extractMergeData(childOfSource, key, subDraft);
            }
        }
        return true;
    }

    // *****************************************************************************************************
    // method writes the differences between xml files all to one HTML file
    // added by PN
    // *****************************************************************************************************
    private void doBulkComparison() {
        // get the output file name
        String fileName = destFolder + "/" + "Bulk.html";
        System.out.println("INFO: Creating file named: " + fileName);
        String fileName_summary = destFolder + "/" + "Bulk_summary.html";
        System.out.println("INFO: Creating file named: " + fileName_summary);

        try {
            OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(fileName), encoding);
            OutputStreamWriter os_summary = new OutputStreamWriter(new FileOutputStream(fileName_summary), encoding);

            // write the beginning of HTML page
            PrintWriter writer = new PrintWriter(os);
            PrintWriter writer_summary = new PrintWriter(os_summary);
            printHTMLStart(writer);
            printInfo(writer);
            printHTMLStart(writer_summary);

            // not all platforms have files for all locales so first build a locale superset
            // loop thru locale files from each folder, each folder contains a certain number of locales
            // build a HashSet superset
            File localeDir = null;
            String[] fileList;
            Set<String> localeTreeSet = new TreeSet<String>(); // use TreeSet for locales in alphabetical order
            for (int i = 0; i < m_PlatformFolderVect.size(); i++) {
                localeDir = new File((String) m_PlatformFolderVect.elementAt(i));
                fileList = localeDir.list();
                for (int j = 0; j < fileList.length; j++) {
                    if (fileList[j].endsWith(".xml")) {
                        // need to exclude root.xml and supplementalData.xml
                        if ((fileList[j].compareTo("root.xml") == 0)
                            || (fileList[j].compareTo("supplementalData.xml") == 0))
                            continue;

                        // exclude common if -diff_ref_common option chosen by user
                        // as common will only be shown as a reference if there are differences between locales for
                        // other platforms
                        if ((m_iOptions & OPT_DIFF_REF_COMMON) != 0) {
                            String platform = (String) m_PlatformVect.elementAt(i);
                            if (platform.compareTo(COMMON) == 0)
                                continue;
                        }

                        // entries are only added to TreeSets if not already there
                        localeTreeSet.add(fileList[j]);
                        // System.out.println (j + " adding " + fileList[j] + " to super set for platform " + (String)
                        // m_PlatformFolderVect.elementAt(i) );
                    }
                }
            }

            // System.out.println(" size of locale set = " + localeTreeSet.size());
            // System.out.println(" number of platforms = " + m_PlatformFolderVect.size() + "(" + m_PlatformVect.size()
            // + ")");

            // loop thru all locales
            Object[] localeArray = localeTreeSet.toArray();
            int i = 0;
            for (i = 0; i < localeArray.length; i++) {
                String platforms_with_this_locale = "";

                String localeFile = (String) localeArray[i]; // locale file name without path
                // class member localeStr used for writing to html
                localeStr = localeFile.substring(0, localeFile.indexOf('.'));
                System.out.println("INFO: locale : " + localeStr);

                // add entry to CompareMap for any platforms having an xml file for the locale in question
                for (int j = 0; j < m_PlatformFolderVect.size(); j++) {
                    localeDir = new File((String) m_PlatformFolderVect.elementAt(j));
                    fileList = localeDir.list();
                    for (int k = 0; k < fileList.length; k++) {
                        if (fileList[k].compareTo(localeFile) == 0) // test for 2 matching xml filenames
                        {
                            String key = (String) m_PlatformVect.elementAt(j); // should use hashtable to link
                            // m_PlatformVect and
                            // m_PlatformFolderVect
                            String xmlFileName = localeDir + "/" + localeArray[i];
                            // System.out.println(i + " " + j + " " + k + " adding " + xmlFileName +
                            // " to compareMap at key " + key);
                            addToCompareMap(xmlFileName, key);

                            if (!(((m_iOptions & OPT_DIFF_REF_COMMON) != 0)
                                && (key.compareTo(COMMON) == 0))) {
                                platforms_with_this_locale += key;
                                platforms_with_this_locale += ",  ";
                            }
                        }
                    }
                }
                // System.out.println("size of compareMap " + compareMap.size());

                // print locale info and table header for this locale
                printHTMLLocaleStart(writer, i, platforms_with_this_locale);
                printTableHeaderForDifferences(writer);

                // now do the comparison for a specific locale
                walkCompareMap(writer, localeStr, platforms_with_this_locale);

                // clear the compareMap before starting next locale
                compareMap.clear();

                // finish html table
                printHTMLLocaleEnd(writer);

            } // end outer for loop on locales

            // print summary data to html summary file
            printLocaleSummaryToHTML(writer_summary);
            printAccumulatedResultsToHTML(writer_summary);

            printHTMLEnd(writer, i);
            printHTMLEnd(writer_summary, i);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("INFO: Finished writing file named: " + fileName);
        System.out.println("INFO: Finished writing file named: " + fileName_summary);
    }

    // added by PN
    private void printHTMLStart(PrintWriter writer) {
        // System.out.println("INFO: Creating the comparison chart ");

        writer.print("<html>\n" +
            "    <head>\n" +
            "        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n" +
            "    </head>\n" +
            "    <style>\n" +
            "         <!--\n" +
            "         table        { border-spacing: 0; border-collapse: collapse; width:100%; \n" +
            "                        border: 1px solid black }\n" +
            "         td, th       { border-spacing: 0; border-collapse: collapse; color: black; \n" +
            "                        vertical-align: top; border: 1px solid black }\n" +
            "         -->\n" +
            "     </style>" +
            "     <body bgcolor=\"#FFFFFF\"> \n" +
            "        <p><b>LOCALE DATA AUDIT</b></p>");

        writer.print("        <p>Created on: " + cal.getTime() + "</p>\n");
    }

    private void printInfo(PrintWriter writer) {
        if (((m_iOptions & OPT_DIFF_REF_COMMON) != 0)
            || ((m_iOptions & OPT_DIFF) != 0)) {
            writer
                .print("        <p>locale elements where there is a difference between at least two platforms are shown. \n"
                    +
                    "If a locale element is the same across all platforms it is not shown </p>");
        }

        if ((m_iOptions & OPT_DIFF_REF_COMMON) != 0)
            writer
                .print("<p>   Common data is shown for reference purposes only and is not part of the comparison</p>\n");

    }

    // added by PN
    private void printHTMLEnd(PrintWriter writer, int iTotalNumLocales) {
        writer.print("<p>&nbsp;</p>");
        writer.print("<p>&nbsp;</p>");
        writer.print("          <p><b>SUMMARY : </b></p>");
        String platforms = "";
        for (int i = 0; i < m_PlatformVect.size(); i++) {
            if (((m_iOptions & OPT_DIFF_REF_COMMON) != 0)
                && (m_PlatformVect.elementAt(i).equals(COMMON))) {
                continue;
            }
            platforms += m_PlatformVect.elementAt(i);
            platforms += ", ";
        }

        writer.print("          <p><b>Platforms compared : " + platforms + "</b></p>");
        writer
            .print("          <p><b>Total Number of locales audited : "
                + iTotalNumLocales
                + "</b></p>"
                +
                "           <p><b>Total Number of conflicting locale data across all locales : "
                + serialNumber
                + "</b></p>"
                +
                "           <p><b>Number of locale elements where a conflict was found for at least one locale : "
                + m_iTotalConflictingElements
                + "</b></p>"
                +
                "           <p><b>Number of locale elements where no conflicts were found for any locale having this element : "
                + m_iTotalNonConflictingElements + "</b></p>" +
                "    </body>\n" +
                "</html>\n");
        writer.flush();

        writer.flush();
    }

    // added by PN
    private void printHTMLLocaleStart(PrintWriter writer, int iLocaleCounter, String platforms_with_this_locale) {
        ULocale locale = new ULocale(localeStr);
        String displayLang = locale.getDisplayLanguage();
        String dispCountry = locale.getDisplayCountry();
        String dispVariant = locale.getDisplayVariant();
        String displayName = localeStr + " (" + displayLang + "_" + dispCountry;
        if (dispVariant.length() > 0) {
            displayName += "_" + dispVariant + ") ";
        } else {
            displayName += ") ";
        }

        writer.print(
            "        <p><b>" + iLocaleCounter + "&nbsp;&nbsp;&nbsp;" + displayName +
            // "<a href=\"http://oss.software.ibm.com/cgi-bin/icu/lx/en/?_="+localeStr+"\">Demo</a>, "+
            // "<a href=\"../comparison_charts.html\">Cover Page</a>, "+
            // "<a href=\"./index.html\">Index</a>, "+
            // "<a href=\"../collation_diff/"+localeStr+"_collation.html\">Collation</a> "+
                "</b>" +
                "<b>&nbsp;&nbsp;&nbsp; platforms with this locale : " + platforms_with_this_locale + "</b></p>\n" +
                "        <table>\n");
    }

    // added by PN
    private void printHTMLLocaleEnd(PrintWriter writer) {
        writer.print("        </table>\n");
    }

    // added by PN
    private void walkCompareMap(PrintWriter writer, String locale, String platforms) {
        SummaryData summData = new SummaryData();

        // walk down the compare map and print the data
        Iterator<String> iter = compareMap.keySet().iterator();
        while (iter.hasNext()) {
            Object obj = iter.next();
            CompareElement element;
            if (obj != null) {
                Object value = compareMap.get(obj);
                if (value instanceof CompareElement) {
                    element = (CompareElement) value;
                } else {
                    throw new RuntimeException(
                        "The object stored in the compare map is not an instance of CompareElement");
                }

                boolean bIsEqual = true;
                if ((m_iOptions & OPT_DIFF) != 0) {
                    bIsEqual = printDifferentValues(element, writer);
                    AddToAccumulatedResultsMap((String) obj, element, localeStr, bIsEqual);
                } else if ((m_iOptions & OPT_DIFF_REF_COMMON) != 0) {
                    bIsEqual = printDifferentValuesWithRef(element, writer);
                    AddToAccumulatedResultsMap((String) obj, element, localeStr, bIsEqual);
                } else {
                    printValue(element, writer);
                }

                if (bIsEqual == false)
                    summData.m_iNumConflictingElements++;

            } else {
                throw new RuntimeException("No objects stored in the compare map!");
            }
        }
        summData.m_szPlatforms = platforms;
        m_LocaleSummaryDataMap.put(locale, summData);

    }

    // PN added
    private void AddToAccumulatedResultsMap(String id, CompareElement element, String locale, boolean bIsEqual) {
        if (element == null)
            return;

        AccumulatedResults ad = m_AccumulatedResultsMap.get(id);
        if (ad == null) {
            // System.out.println("id = " + id);

            // add a new entry, there's none there with this key
            ad = new AccumulatedResults();
            ad.index = element.index;
            ad.node = element.node;
            ad.parentNode = element.parentNode;
            if (bIsEqual == false)
                ad.localeVectDiff.add(locale);
            else
                ad.localeVectSame.add(locale);
            m_AccumulatedResultsMap.put(id, ad);
        } else {
            if ((!ad.index.equals(element.index)) ||
                (!ad.node.equals(element.node)) ||
                (!ad.parentNode.equals(element.parentNode))) // ||
            // (!ad.type.equals(element.type))) type can be null so don't ceck its value
            {
                throw new RuntimeException(
                    "The retrieved AccumulatedResults is not the same as the one trying to be saved - " + id);
            } else {
                if (bIsEqual == false)
                    ad.localeVectDiff.add(locale);
                else
                    ad.localeVectSame.add(locale);
            }
        }
    }

    private void printAccumulatedResultsToHTML(PrintWriter writer) {
        writer.print("<p>&nbsp;</p>");
        writer.print("<p>&nbsp;</p>");
        writer
            .print("<p><b>Table below shows the number of locales where conflicts did and didn't occur on a per locale element basis");
        writer
            .print("&nbsp;&nbsp; (For brevity, locale elements where no conflicts were detected for any locale are not shown) </b></p>");
        writer.print("<p></p>");
        writer.print("      <table width=\"700\">\n");
        writer.print("            <tr>\n" +
            "                <th width=5%>N.</th>\n" +
            "                <th width=10%>ParentNode</th>\n" +
            "                <th width=10%>Name</th>\n" +
            "                <th width=10%>ID</th>\n" +
            "                <th width=10%># of non-conflicting locales</th>" +
            "                <th width=10%># of conflicting locales</th>" +
            "                <th width=45%>Locales where conflicts were found</th>" +
            "            </tr>\n");

        // walk down the cm_AccumulateDifferenceMap and print the data
        Iterator<String> iter = m_AccumulatedResultsMap.keySet().iterator();
        // System.out.println ("size = " + m_AccumulateDifferenceMap.size());

        int iCounter = 0;
        while (iter.hasNext()) {
            Object obj = iter.next();
            AccumulatedResults ad;
            if (obj != null) {
                Object value = m_AccumulatedResultsMap.get(obj);
                if (value instanceof AccumulatedResults) {
                    ad = (AccumulatedResults) value;
                } else {
                    throw new RuntimeException(
                        "The object stored in the AccumulateDifferencesMap is not an instance of AccumulateDifferences");
                }

                // only print locale elements where differences occurred
                if (ad.localeVectDiff.size() > 0) {
                    m_iTotalConflictingElements++;
                    writer.print("            <tr>\n");
                    writer.print("                <td>" + (iCounter++) + "</td>\n");
                    writer.print("                <td>" + ad.parentNode + "</td>\n");
                    writer.print("                <td>" + ad.node + "</td>\n");
                    writer.print("                <td>" + ad.index + "</td>\n");
                    writer.print("                <td>" + ad.localeVectSame.size() + "</td>\n");
                    writer.print("                <td>" + ad.localeVectDiff.size() + "</td>\n");
                    String locales = "";
                    for (int i = 0; i < ad.localeVectDiff.size(); i++) {
                        locales += ad.localeVectDiff.elementAt(i);
                        locales += ", ";
                    }
                    writer.print("                <td>" + locales + "</td>\n");
                    writer.print("            </tr>\n");
                } else {
                    m_iTotalNonConflictingElements++;
                }

            } else {
                throw new RuntimeException("No objects stored in the AccumulateDifferencesMap!");
            }
        }

        writer.print("      </table>\n");

    }

    private void printLocaleSummaryToHTML(PrintWriter writer) {
        writer.print("<p>&nbsp;</p>");
        writer.print("<p>&nbsp;</p>");
        writer.print("<p><b>Table below shows the number of conflicting elements on a per locale basis\n</b></p>");
        writer.print("<p></p>");
        writer.print("      <table width=\"700\">\n");
        writer.print("            <tr>\n" +
            "                <th width=5%>N.</th>\n" +
            "                <th width=20%>Locale</th>\n" +
            "                <th width=40%>Platforms With This Locale</th>\n" +
            "                <th width=35%># of elements where a conflict was found</th>\n" +
            "            </tr>\n");

        // walk down the cm_AccumulateDifferenceMap and print the data
        Iterator<String> iter = m_LocaleSummaryDataMap.keySet().iterator();
        int iCounter = 0;
        while (iter.hasNext()) {
            Object obj = iter.next();
            SummaryData summData;
            if (obj != null) {
                Object value = m_LocaleSummaryDataMap.get(obj);
                if (value instanceof SummaryData) {
                    summData = (SummaryData) value;
                } else {
                    throw new RuntimeException(
                        "The object stored in the AccumulateDifferencesMap is not an instance of AccumulateDifferences");
                }

                writer.print("            <tr>\n");
                writer.print("                <td>" + (iCounter++) + "</td>\n");
                writer.print("                <td>" + (String) obj + "</td>\n");
                writer.print("                <td>" + summData.m_szPlatforms + "</td>\n");
                writer.print("                <td>" + summData.m_iNumConflictingElements + "</td>\n");
                writer.print("            </tr>\n");
            } else {
                throw new RuntimeException("No objects stored in the AccumulateDifferencesMap!");
            }
        }

        writer.print("      </table>\n");
    }

    private void getCVSVersion() {
        ourCvsVersion = LDMLUtilities.loadFileRevision(goldFileName);
    }

} // end of class definition/declaration
