/*
**********************************************************************
* Copyright (c) 2002-2004, International Business Machines
* Corporation and others.  All Rights Reserved.
**********************************************************************
* Author: Mark Davis
**********************************************************************
*/
package org.unicode.cldr.icu;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.ContentHandler;
import org.xml.sax.ext.DeclHandler;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.XMLReader;

import org.unicode.cldr.util.CachingEntityResolver;

import com.ibm.icu.dev.test.util.BagFormatter;
import com.ibm.icu.dev.test.util.TransliteratorUtilities;
import com.ibm.icu.dev.tool.UOption;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.Collator;
import com.ibm.icu.text.RuleBasedCollator;
import com.ibm.icu.text.UnicodeSet;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;

//import javax.xml.parsers.*;

/**
 * This is a simple class that walks through the CLDR hierarchy and does 2 things.
 * First, it determines all the places where the CLDR is not minimal: where there
 * are redundancies with inheritance. It generates new files in the target directory.
 * Second, it gathers together all the items from all the locales that share the
 * same element chain, and thus presents a "sideways" view of the data, in files called
 * by_type/X.html, where X is a type. X may be the concatenation of more than more than
 * one element, where the file would otherwise be too large.
 * @author medavis
 */
/*
Notes:
http://xml.apache.org/xerces2-j/faq-grammars.html#faq-3
http://developers.sun.com/dev/coolstuff/xml/readme.html
http://lists.xml.org/archives/xml-dev/200007/msg00284.html
http://java.sun.com/j2se/1.4.2/docs/api/org/xml/sax/DTDHandler.html
 */
public class GenerateSidewaysView {
    // debug flags
    static final boolean DEBUG = false;
    static final boolean DEBUG2 = false;
    static final boolean DEBUG_SHOW_ADD = false;
    static final boolean DEBUG_ELEMENT = false;
    static final boolean DEBUG_SHOW_BAT = false;

    static final boolean FIX_ZONE_ALIASES = true;

    private static final int
        HELP1 = 0,
        HELP2 = 1,
        SOURCEDIR = 2,
        DESTDIR = 3,
        MATCH = 4,
        SKIP = 5,
        TZADIR = 6,
        NONVALIDATING = 7,
        SHOW_DTD = 8,
		TRANSLIT = 9;

    private static final String NEWLINE = "\n";

    private static final UOption[] options = {
            UOption.HELP_H(),
            UOption.HELP_QUESTION_MARK(),
            UOption.SOURCEDIR().setDefault("C:\\ICU4C\\locale\\common\\main\\"),
            UOption.DESTDIR().setDefault("C:\\DATA\\GEN\\cldr\\main\\"),
            UOption.create("match", 'm', UOption.REQUIRES_ARG).setDefault(".*"),
            UOption.create("skip", 'z', UOption.REQUIRES_ARG).setDefault("zh_(C|S|HK|M).*"),
            UOption.create("tzadir", 't', UOption.REQUIRES_ARG).setDefault("C:\\ICU4J\\icu4j\\src\\com\\ibm\\icu\\dev\\tool\\cldr\\"),
            UOption.create("nonvalidating", 'n', UOption.NO_ARG),
            UOption.create("dtd", 'w', UOption.NO_ARG),
            UOption.create("transliterate", 'y', UOption.NO_ARG),
    };
    private static String timeZoneAliasDir = null;

    public static void main(String[] args) throws SAXException, IOException {
        UOption.parseArgs(args, options);

        Matcher skipper = Pattern.compile(options[SKIP].value).matcher("");
        Matcher matcher = Pattern.compile(options[MATCH].value).matcher("");
        //matcher = Pattern.compile("(root|b).*").matcher("");
        log = BagFormatter.openUTF8Writer(options[DESTDIR].value, "log.txt");
        timeZoneAliasDir = options[TZADIR].value;
        try {
            File sourceDir = new File(options[SOURCEDIR].value);
            String[] contents = sourceDir.list();
            for (int i = 0; i < contents.length; ++i) {
                if (!contents[i].endsWith(".xml")) continue;
                if (contents[i].startsWith("supplementalData")) continue;
                if (!matcher.reset(contents[i]).matches()) continue; // debug shutoff
                if (skipper.reset(contents[i]).matches()) continue; // debug shutoff
                //System.out.println("Processing " + contents[i]);
                log.println();
                log.println("Processing " + contents[i]);
                String baseName = contents[i].substring(0,contents[i].length()-4);
                GenerateSidewaysView temp = getCLDR(baseName, !options[NONVALIDATING].doesOccur);
                // if (baseName.equals("zh_TW")) baseName = "zh_Hant_TW";
                // if (baseName.equals("root")) temp.addMissing();
                if (options[SHOW_DTD].doesOccur) temp.writeDTDCheck();
                temp.writeTo(options[DESTDIR].value, baseName);
                org.unicode.cldr.util.Utility.generateBat(options[SOURCEDIR].value, baseName + ".xml", options[DESTDIR].value, 
                		baseName + ".xml", null);
                sidewaysView.putData(temp.data, baseName);
                log.flush();
            }
            sidewaysView.showCacheData();
        } finally {
            log.close();
            System.out.println("Done");
       }
    }

    /**
     *
     */
    private void writeDTDCheck() {
        DEFAULT_DECLHANDLER.checkData();
    }

    static Collator DEFAULT_COLLATION = null;

    static final Set IGNOREABLE =  new HashSet(Arrays.asList(new String[] {
            "draft",
            //"references",
            //"standard"
    }));

    static final Set IGNORELIST = new HashSet(Arrays.asList(new String[] {
            "draft", "standard", "references", "validSubLocales"
    }));

    static final Set LEAFNODES = new HashSet(Arrays.asList(new String[] {
                   "alias", "default", "firstDay", "mapping", "measurementSystem",
                   "minDays", "orientation", "settings", "weekendStart", "weekendEnd"
   }));

    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;
    }

    public static class TimeZoneAliases {
        static Map map = null;
        static void init() {
            map = new HashMap();
            try {
                BufferedReader br = BagFormatter.openUTF8Reader(timeZoneAliasDir, "timezone_aliases.txt");
                String[] pieces = new String[2];
                while (true) {
                    String line = br.readLine();
                    if (line == null) break;
                    Utility.split(line,';', pieces);
                    map.put(pieces[0].trim(), pieces[1].trim());
                }
                br.close();
                map.put("","EMPTY-REMOVE");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        public static String get(Object id) {
            if (map == null) init();
            return (String) map.get(id);
        }
    }

    static MapComparator elementOrdering = new MapComparator();
    static MapComparator attributeOrdering = new MapComparator();
    {
        // hack the ordering of these two
        attributeOrdering.add("alt");
        attributeOrdering.add("draft");
    }
    static MapComparator valueOrdering = new MapComparator();

    OrderedMap data = new OrderedMap();
    MyContentHandler DEFAULT_HANDLER = new MyContentHandler();
    MyDeclHandler DEFAULT_DECLHANDLER = new MyDeclHandler();
    XMLReader xmlReader;

    /*SAXParser SAX;
    {
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setValidating(true);

            SAX = factory.newSAXParser();
        } catch (Exception e) {
            throw new IllegalArgumentException("can't start");
        }
    }
    */

    static PrintWriter log;

    /**
     *
     */
    /*private void addMissing() {
        String[] currencies = getCodes(new ULocale("en","",""), "Currencies");
        //<ldml><numbers><currencies><currency type="AUD"><displayName>
        addCurrencies(currencies, "displayName");
        addCurrencies(currencies, "symbol");
    }
    */

    /*
    private void addCurrencies(String[] currencies, String lastElement) {
        ElementChain temp = new ElementChain();
        temp.push("ldml",null).push("numbers",null).push("currencies",null)
            .push("currency",null).push(lastElement,null,null);
        for (int i = 0; i < currencies.length; ++i) {
            temp.setAttribute("currency","type",currencies[i]);
            String value = (String) data.get(temp);
            if (value != null) continue;
            putData(temp, currencies[i]);
        }
    }
    */

    // UGLY hack
    private static String[] getCodes(ULocale locale, String tableName) {
        // TODO remove Ugly Hack
        // get stuff
        ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(locale);
        ICUResourceBundle table = bundle.getWithFallback(tableName);
        // copy into array
        ArrayList stuff = new ArrayList();
        for (Enumeration keys = table.getKeys(); keys.hasMoreElements();) {
            stuff.add(keys.nextElement());
        }
        String[] result = new String[stuff.size()];
        return (String[]) stuff.toArray(result);
        //return new String[] {"Latn", "Cyrl"};
    }


    static Map cache = new HashMap();

    static GenerateSidewaysView getCLDR(String s, boolean validating) throws SAXException, IOException {
        GenerateSidewaysView temp = (GenerateSidewaysView)cache.get(s);
        if (temp == null) {
            temp = new GenerateSidewaysView(s, validating);
            cache.put(s,temp);
        }
        return temp;
    }

    String filename;
    GenerateSidewaysView parent = null;

    private GenerateSidewaysView(String filename, boolean validating) throws SAXException, IOException {
        this.filename = filename;
        xmlReader = createXMLReader(validating);
        // make sure the parents are loaded into cache before we are
        String parentName = getParentFilename(filename);
        if (parentName != null) {
            parent = getCLDR(parentName, validating);
        }
        //System.out.println("Creating " + filename);

        xmlReader.setContentHandler(DEFAULT_HANDLER);
        xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler",DEFAULT_HANDLER);
        if (options[SHOW_DTD].doesOccur) xmlReader.setProperty("http://xml.org/sax/properties/declaration-handler", DEFAULT_DECLHANDLER);

        readFrom(options[SOURCEDIR].value, filename);
        // walk through the map removing anything that is inherited from a parent.
        // changed so that we stop when the parent has an element
        if (parent != null) {
            Parent parentOut = new Parent();
            Map toRemove = new TreeMap();
            for (Iterator it = data.iterator(); it.hasNext();) {
                Object key = it.next();
                if (((ElementChain)key).containsElement("identity")) continue;
                EndNode value = (EndNode) data.get(key);
                Object inheritedValue = getInheritedValue(key, parentOut);
                if (value.equals(inheritedValue)) {
                    // debugging: data.get(key);
                    //getInheritedValue(key);
                    //value.equals(inheritedValue);
                    toRemove.put(key, parentOut.parent);
                }
            }
            for (Iterator it = toRemove.keySet().iterator(); it.hasNext();) {
                ElementChain key = (ElementChain)it.next();
                EndNode value = (EndNode) data.get(key);
                GenerateSidewaysView parent = (GenerateSidewaysView) toRemove.get(key);
                EndNode parentValue = (EndNode) parent.data.get(key);
                log.println("Removing " + key.toString(true, 0, Integer.MAX_VALUE) + "\t" + value);
                ElementChain parentKey = (ElementChain) parent.data.getKeyFor(key);
                log.println("\tIn " + parent.filename + ":\t" + parentKey.toString(true, 0, Integer.MAX_VALUE) + "\t"+ parentValue);
                data.remove(key);
            }
        }
    }

    static class Parent {
        GenerateSidewaysView parent;
    }
    private Object getInheritedValue(Object key, Parent parentOut) {
        if (parent == null) {
            parentOut.parent = null;
            return null;
        }
        EndNode value = (EndNode) parent.data.get(key);
        if (value != null) {
            parentOut.parent = parent;
            return value;
        }
        return parent.getInheritedValue(key, parentOut);
    }

    /*
    Set badTimezoneIDs = null;
     private void detectAliases(String filename) {

        Set problems = new TreeSet();
        for (Iterator it = data.iterator(); it.hasNext();) {
            ElementChain key = (ElementChain) it.next();
            for (int i = 0; i < key.contexts.size(); ++i) {
                Element e = (Element) key.contexts.get(i);
                if (!e.elementName.equals("zone")) continue;
                for (Iterator q = e.attributes.contents.iterator(); q.hasNext(); ) {
                    SimpleAttribute a = (SimpleAttribute)q.next();
                    if (!a.name.equals("type")) continue;
                    String other = TimeZoneAliases.get(a.value);
                    if (other != null) {
                        problems.add(a.value);
                    }
                }
            }
        }

        for (Iterator it = badTimezoneIDs.iterator(); it.hasNext();) {
            String oldOne = (String)it.next();
            String newOne = TimeZoneAliases.get(oldOne);
            log.println("Fix Timezone Alias: " + filename + "\t" + oldOne + " => " + newOne);
        }
    } */

    /*
    private void removeAll(GenerateSidewaysView temp) {
        data.removeAll(temp.data);
    }
    */

    private static String getParentFilename(String filename) {
        if (filename.equals("zh_TW")) return "zh_Hant";
        int pos = filename.lastIndexOf('_');
        if (pos >= 0) {
            return filename.substring(0,pos);
        }
        if (filename.equals("root")) return null;
        return "root";
    }

    private void writeTo(String dir, String filename) throws IOException {
        PrintWriter out = BagFormatter.openUTF8Writer(dir, filename + ".xml");
        out.println(this);
        out.close();
    }

    public void readFrom(String dir, String filename) throws SAXException, IOException {
        File f = new File(dir, filename + ".xml");
        System.out.println("Parsing: " + f.getCanonicalPath());
        log.println("Parsing: " + f.getCanonicalPath());
        final FileInputStream fileInputStream = new FileInputStream(f);
        final InputSource is = new InputSource(fileInputStream);
        is.setSystemId(f.getCanonicalPath());
        xmlReader.parse(is);
        fileInputStream.close();
        //SAX.parse(f, DEFAULT_HANDLER);
    }

    private Set findDuplicateZoneIDs() {
        Set result = new HashSet();
        // if a set contains both EST and America/Indianapolis, remove the former
        for (Iterator it = zoneIDs.iterator(); it.hasNext();) {
            Object possibleOmission = it.next();
            Object o = TimeZoneAliases.get(possibleOmission);
            if (o == null) continue;
            if (zoneIDs.contains(o)) result.add(possibleOmission);
        }
        return result;
    }

    public String toString() {
        StringBuffer buffer = new StringBuffer();

        buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"+NEWLINE
        + "<!DOCTYPE ldml SYSTEM \"http://www.unicode.org/cldr/dtd/1.2/beta/ldml.dtd\">"+NEWLINE);

        Set duplicateZoneIDs = findDuplicateZoneIDs();
        //badTimezoneIDs = new TreeSet();

        ElementChain empty = new ElementChain();
        ElementChain old = empty;
        for (Iterator it = data.iterator(); it.hasNext();) {
            ElementChain key = (ElementChain) it.next();
            if (FIX_ZONE_ALIASES) {
                Element zoneElement = key.getElement("zone");
                if (zoneElement != null) {
                    String zoneTypeValue = zoneElement.getValue("type");
                    if (zoneTypeValue != null) {
                        if (duplicateZoneIDs.contains(zoneTypeValue)) continue;
                    }
                }
            }
            if (true) {
                // weekendEnd draft="true" time="00:00" NEVER ok
                Element zoneElement = key.getElement("weekendEnd");
                if (zoneElement != null) {
                    String zoneTypeValue = zoneElement.getValue("time");
                    if ("00:00".equals(zoneTypeValue)) {
                        log.println("BAD WEEKENDEND TIME: " + zoneTypeValue);
                    }
                }
            }
            EndNode value = (EndNode) data.get(key);
            key.writeDifference(old, value, buffer);
            old = key;
        }
        empty.writeDifference(old, null, buffer);
        writeElementComment(buffer,finalComment,0);

        return buffer.toString();
    }

/*    static void generateBat(String sourceDir, String sourceFile, String targetDir, String targetFile) {
        boolean needBat = true;
        try {
            BufferedReader b1 = BagFormatter.openUTF8Reader(sourceDir, sourceFile);
            BufferedReader b2 = BagFormatter.openUTF8Reader(targetDir, targetFile);
            while (true) {
                String line1 = b1.readLine();
                String line2 = b2.readLine();
                if (line1 == null && line2 == null) {
                    needBat = false;
                    break;
                }
                if (line1 == null || line2 == null) {
                    if (DEBUG_SHOW_BAT) System.out.println("*File line counts differ: ");
                    break;
                }
                if (!equalsIgnoringWhitespace(line1, line2)) {
                    if (DEBUG_SHOW_BAT) {
                        System.out.println("*File lines differ: ");
                        System.out.println("\t1\t" + line1);
                        System.out.println("\t2\t" + line2);
                    }
                    break;
                }
            }
            b1.close();
            b2.close();
            String batDir = targetDir + File.separator + "diff" + File.separator;
            String batName = targetFile + ".bat";
            if (needBat) {
                PrintWriter bat = BagFormatter.openUTF8Writer(batDir, batName);
                bat.println("\"C:\\Program Files\\Compare It!\\wincmp3.exe\" " +
                        new File(sourceDir + sourceFile).getCanonicalPath() + " " +
                        new File(targetDir + targetFile).getCanonicalPath());
                bat.close();
            } else {
                File f = new File(batDir + batName);
                if (f.exists()) {
                    if (DEBUG_SHOW_BAT) System.out.println("*Deleting old " + f.getCanonicalPath());
                    f.delete();
                }
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

     //

    }
*/
    static boolean equalsIgnoringWhitespace(String a, String b) {
        int i = 0;
        int j = 0;
        char c, d;
        while (true) { // don't worry about surrogates
            do {c = i < a.length() ? a.charAt(i++) : 0xFFFF;} while (UCharacter.isUWhiteSpace(c));
            do {d = j < b.length() ? b.charAt(j++) : 0xFFFF;} while (UCharacter.isUWhiteSpace(d));
            if (c != d) return false;
            if (c == 0xFFFF) return true;
        }
    }

    static class SimpleAttribute implements Comparable {
        String name;
        String value;
        SimpleAttribute(String name, String value) {
            attributeOrdering.add(name);
            valueOrdering.add(value);
            this.name = name;
            this.value = value;
        }
        public boolean equals(Object other) {
            return compareTo(other) == 0;
        }
        public int hashCode() {
            return name.hashCode() ^ value.hashCode();
        }
        public String toString() {return toString(true);}
        public String toString(boolean path) {
            if (path) {
                return "[@" + name + "='" + TransliteratorUtilities.toHTML.transliterate(value) + "']";
            } else {
                return " " + name + "=\"" + TransliteratorUtilities.toXML.transliterate(value) + "\"";
            }
        }
        public int compareTo(Object o) {
            SimpleAttribute that = (SimpleAttribute) o;
            int result;
            if ((result = attributeOrdering.compare(name, that.name)) != 0) return result;
            return valueOrdering.compare(value, that.value);
        }
    }

    Set zoneIDs = new HashSet();

    class SimpleAttributes implements Comparable {
        Set contents = new TreeSet();

        SimpleAttributes() {}

        SimpleAttributes(SimpleAttributes other, String elementName) {
            contents.clear();
            contents.addAll(other.contents);
        }

        SimpleAttributes(Attributes attributes, String elementName) {
            boolean inZone = elementName.equals("zone");
            if (attributes != null) {
                for (int i = 0; i < attributes.getLength(); ++i) {
                    String name = attributes.getQName(i);
                    String value = attributes.getValue(i);

                    if (FIX_ZONE_ALIASES && inZone) {
                        if (name.equals("type")) {
                            zoneIDs.add(value);
                        }
                    }

                    // hack to removed #IMPLIED
                    if (elementName.equals("ldml")
                        && name.equals("version")) continue; // skip version
                    if (name.equals("type")
                        && value.equals("standard")) continue;

                    contents.add(new SimpleAttribute(name, value));
                    tripleData.recordData(elementName, name, value);
                }
            }
        }

        public String toString() {return toString(true, false);}
        public String toString(boolean path, boolean isZone) {
            StringBuffer buffer = new StringBuffer();
            for (Iterator it = contents.iterator(); it.hasNext();) {
                SimpleAttribute a = (SimpleAttribute) it.next();
                //SimpleAttribute a = getSkipping(it);
                //if (a == null) continue;
                //SimpleAttribute a = (SimpleAttribute)it.next();
                //if (path && IGNOREABLE.contains(a.name)) continue;
                if (isZone && a.name.equals("type")) {
                    String replacement = TimeZoneAliases.get(a.value);
                    if (replacement != null) a = new SimpleAttribute("type", replacement);
                }
                buffer.append(a.toString(path));
            }
            return buffer.toString();
        }
        public int compareTo(Object o) {
            // IGNORE draft, source, reference
            SimpleAttributes that = (SimpleAttributes) o;
            // quick check for common case
            if (contents.size() == 0 && that.contents.size() == 0) return 0;
            // compare one at a time. Stop if one element is less than another.
            Iterator it = contents.iterator();
            Iterator it2 = that.contents.iterator();
            int result;
            while (true) {
                SimpleAttribute a = getSkipping(it);
                SimpleAttribute a2 = getSkipping(it2);
                if (a == null) {
                    if (a2 == null) return 0;
                    return -1;
                }
                if (a2 == null) {
                    return 1;
                }
                if ((result = a.compareTo(a2)) != 0) return result;
            }
        }
        private SimpleAttribute getSkipping(Iterator it) {
            while (it.hasNext()) {
                SimpleAttribute a = (SimpleAttribute)it.next();
                if (!IGNOREABLE.contains(a.name)) return a;
            }
            return null;
        }

        /**
         * @param attribute
         * @param value
         */
        public SimpleAttributes set(String element, String attribute, String value) {
            for (Iterator it = contents.iterator(); it.hasNext();) {
                SimpleAttribute sa = (SimpleAttribute) it.next();
                if (sa.name.equals(attribute)) {
                    contents.remove(sa);
                    break;
                }
            }
            contents.add(new SimpleAttribute(attribute, value));
            tripleData.recordData(element, attribute, value);
            return this;
        }

        /**
         * @param attributeName
         * @return
         */
        public String getValue(String attributeName) {
            for (Iterator it = contents.iterator(); it.hasNext();) {
                SimpleAttribute sa = (SimpleAttribute) it.next();
                if (sa.name.equals(attributeName)) {
                    return sa.value;
                }
            }
            return null;
        }

        /**
         * @param attributes
         */
        public void add(SimpleAttributes attributes) {
            contents.addAll(attributes.contents);
        }

        /**
         * @param ignorelist
         */
        public void removeAttributes(Set ignorelist) {
            for (Iterator it = contents.iterator(); it.hasNext();) {
                SimpleAttribute sa = (SimpleAttribute) it.next();
                if (ignorelist.contains(sa.name)) {
                    it.remove();
                }
            }
        }
    }

    static class TripleData {
        Map elementToAttributeToValues = new TreeMap(getDefaultCollation());
        Map bigguys = null;
        static final String ANYSTRING = "[any]";

        private void recordData(String element, String attribute, String value) {
            if (!element.equals(ANYSTRING) && IGNORELIST.contains(attribute)) return;
            if (bigguys == null) init();
            // record for posterity
            Map elementToAttribute = (Map) elementToAttributeToValues.get(element);
            if (elementToAttribute == null) {
                elementToAttribute = new TreeMap(getDefaultCollation());
                elementToAttributeToValues.put(element, elementToAttribute);
            }
            Set valueSet = (Set) elementToAttribute.get(attribute);
            if (valueSet == null) {
                valueSet = new TreeSet(getDefaultCollation());
                elementToAttribute.put(attribute, valueSet);
            }
            if (value != null) valueSet.add(value);
        }

        /**
         * @throws IOException
         *
         */
        private void writeData() throws IOException {
            String fileName = "attributeList.html";
            PrintWriter out = BagFormatter.openUTF8Writer(options[DESTDIR].value, "by_type" + File.separator + fileName);
            out.println("<html><head>");
            out.println("<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>");
            out.println("<title>Element/Attribute/Value</title>");
            out.println("<link rel='stylesheet' type='text/css' href='http://oss.software.ibm.com/cvs/icu/~checkout~/icuhtml/common.css'>");
            out.println("<link rel='stylesheet' type='text/css' href='by_type.css'>");
            out.println("</head><body>");
            out.println("<table>");
            for (int i = 0; i < fixList.length; ++i) {
                Object[] row = fixList[i];
                String elementItem = (String)row[0];
                if (elementItem.equals("*")) continue;
                String attributeItem = (String)row[1];
                if (attributeItem.equals("*")) continue;
                recordData(elementItem, attributeItem, null);
            }
            for (Iterator it = elementToAttributeToValues.keySet().iterator(); it.hasNext();) {
                String element = (String) it.next();
                boolean newElement = true;
                Map elementToAttribute = (Map) elementToAttributeToValues.get(element);
                Set attributeKeys = elementToAttribute.keySet();
                for (Iterator it2 = attributeKeys.iterator(); it2.hasNext();) {
                    String attribute = (String) it2.next();
                    boolean newAttribute = true;
                    Collection valueSet = getValues(element, elementToAttribute, attribute);
                    out.println("<tr>");
                    if (newElement) {
                        out.print("<td rowSpan='"
                                + attributeKeys.size()
                                + "'>" + element + "</td>");
                        out.println("<!-- " + valueSet.size() + ", "  + attributeKeys.size() + "-->");
                        newElement = false;
                    }
                    if (newAttribute) {
                        out.print("<td>" + attribute + "</td>");
                        out.println("<!-- " + valueSet.size() + "-->");
                        newAttribute = false;
                    }
                    out.print("<td>");
                    boolean newSet = true;
                    for (Iterator it3 = valueSet.iterator(); it3.hasNext();) {
                        String value = (String) it3.next();
                        if (newSet) newSet = false;
                        else out.print(", ");
                        out.print(TransliteratorUtilities.toHTML.transliterate("\"" + value + "\""));
                    }
                    out.println("</td></tr>");
                    /*
                    for (Iterator it3 = valueSet.iterator(); it3.hasNext();) {
                        String value = (String) it3.next();
                        out.println("<tr>");
                        if (newElement) {
                            out.print("<td rowSpan='"
                                    + getValueCount(elementToAttribute)
                                    + "'>" + element + "</td>");
                            out.println("<!-- " + valueSet.size() + ", "  + attributeKeys.size() + "-->");
                            newElement = false;
                        }
                        if (newAttribute) {
                            out.print("<td rowSpan='" + valueSet.size() + "'>" + attribute + "</td>");
                            out.println("<!-- " + valueSet.size() + "-->");
                            newAttribute = false;
                        }
                        out.println("<td>" + BagFormatter.toHTML.transliterate("\"" + value + "\"") + "</td></tr>");
                    }
                    */
                }
            }
            out.println("</table>");
            writeFooterAndClose(out);
        }

        Object[][] fixList = {
                {"alias", "path", "<valid XPath within locale tree>"},
                {"alias", "source", "<valid locale ID>"},
                {"day", "type", new String[] {"sun", "mon", "tue", "wed", "thu", "fri", "sat"}},
                {"era", "type", "<non-negative number>"},
                {"*", "day", new String[] {"sun", "mon", "tue", "wed", "thu", "fri", "sat"}},
                {"generation", "date", "<yyyy-MM-dd format>"},
                {"version", "number", "<n.m format>"},
                {"*", "time", "<HH:mm (00:00..24:00)>"},
                {"orientation", "*", new String[] {"left-to-right", "right-to-left", "top-to-bottom", "bottom-to-top"}},
                {"minDays", "count", new String[] {"1", "2", "3", "4", "5", "6", "7"}},
                {"language", "type", "%%%language"},
                {"script", "type", "%%%script"},
                {"territory", "type", "%%%region"},
                {"variant", "type", "%%%variant"},
                {"zone", "type", "%%%tzid"},
                {"currency", "type", "%%%currency"},
                {"calendar", "type", new String[] {
                        "buddhist", "chinese", "gregorian", "hebrew", "islamic", "islamic-civil", "japanese"
                        ,"arabic[alias]", "civil-arabic[alias]", "thai-buddhist[alias]"
                }},
                {"measurementSystem", "type", new String[] {"metric", "US", "UK"}},
                {"type", "type", "<any type value--with appropriate key>"},
                {"type", "key", "<any element name having 'type' attribute>"},
                {"key", "type", "<any element name having 'type' attribute>"},

                {ANYSTRING, "draft", new String[] {"true", "approved", "provisional", "unconfirmed", "false*"}},
                {ANYSTRING, "alt", new String[] {"proposed", "variant"}},
                {ANYSTRING, "references", "<list of references>"},
                {ANYSTRING, "standard", "<list of standards>"},
                {ANYSTRING, "validSubLocales", "<list of sub-locales>"},

                {"collation", "type", new String[] {"phonebook", "traditional", "direct", "pinyin", "stroke", "posix", "big5han", "gb2312han"}},
                {"settings", "strength", new String[] {"primary", "secondary", "tertiary", "quaternary", "identical"}},
                {"settings", "alternate", new String[] {"non-ignorable", "shifted"}},
                {"settings", "backwards", new String[] {"on", "off"}},
                {"settings", "normalization", new String[] {"on", "off"}},
                {"settings", "caseLevel", new String[] {"on", "off"}},
                {"settings", "caseFirst", new String[] {"upper", "lower", "off"}},
                {"settings", "hiraganaQuarternary", new String[] {"on", "off"}},
                {"settings", "numeric", new String[] {"on", "off"}},
                {"reset", "before", new String[] {"primary", "secondary", "tertiary"}},

                {"default", "choice", "<any type value legal for one of the peer elements>"},
                {"mapping", "registry", "<any charset registry, iana preferred>"},
                {"mapping", "choice", "<any valid charset from the given registry>"},

                {"abbreviationFallback", "choice", new String[] {"standard", "GMT"}},
                {"preferenceOrdering", "choice", "<space-delimited list of timezone IDs>"},
                {"exemplarCharacters", "type", new String[] {"standard", "auxiliary"}},

                {"decimalFormatLength", "type", new String[] {"full", "long", "medium", "short"}},
                {"scientificFormatLength", "type", new String[] {"full", "long", "medium", "short"}},
                {"currencyFormatLength", "type", new String[] {"full", "long", "medium", "short"}},
                {"percentFormatLength", "type", new String[] {"full", "long", "medium", "short"}},

                {"field", "type", new String[] {"era", "year", "month", "week", "day", "weekday", "dayperiod", "hour", "minute", "second", "zone"}},
                {"relative", "type", "<integer>"},

                {"pattern", "type", "<valid pattern for format>"},

                {"dateFormat", "type", new String[] {"standard", "<special-key>"}},
                {"timeFormat", "type", new String[] {"standard", "<special-key>"}},
                {"dateTimeFormat", "type", new String[] {"standard", "<special-key>"}},
                {"decimalFormat", "type", new String[] {"standard", "<special-key>"}},
                {"scientificFormat", "type", new String[] {"standard", "<special-key>"}},
                {"percentFormat", "type", new String[] {"standard", "<special-key>"}},
                {"currencyFormat", "type", new String[] {"standard", "<special-key>"}},
        };

        String[] pieces = new String[50];

        private void init() {
            try {
                bigguys = new HashMap();
                BufferedReader br = BagFormatter.openUTF8Reader(timeZoneAliasDir, "idList.txt");
                while (true) {
                    String line = br.readLine();
                    if (line == null) break;
                    line = line.trim();
                    if (line.startsWith("#")) continue;
                    Utility.split(line,';', pieces);
                    String tag = pieces[0].trim();
                    String id = pieces[1].trim();
                    addTagValue(tag, id);
                }
                br.close();
                // hack some extras
                addTagValue("language", "root");
                addTagValue("script", "Qaai");
                addTagValue("variant", "POSIX");
                addTagValue("variant", "REVISED");
                addTagValue("variant", "bokmal");
                addTagValue("variant", "nynorsk");
                addTagValue("variant", "aaland");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * @param tag
         * @param id
         */
        private void addTagValue(String tag, String id) {
            Collection c = (Collection) bigguys.get(tag);
            if (c == null) {
                c = new TreeSet();
                bigguys.put(tag, c);
            }
            Utility.split(id, ',', pieces);
            for (int i = 0; i < pieces.length; ++i) {
                if (pieces[i].length() == 0) continue;
                c.add(pieces[i].trim());
            }
        }

        /**
         * @param elementToAttribute
         * @param attribute
         * @return
         */
        private Collection getValues(String element, Map elementToAttribute, String attribute) {
            for (int i = 0; i < fixList.length; ++i) {
                Object[] row = fixList[i];
                String elementItem = (String)row[0];
                String attributeItem = (String)row[1];
                if ((attribute.equals(attributeItem) || "*".equals(attributeItem))
                        && (element.equals(elementItem) || "*".equals(elementItem))) {
                    if (row[2] instanceof String) {
                        String valueItem = (String)row[2];
                        if (valueItem.startsWith("%%%")) {
                            //System.out.println("Substituting Values: " + element + ", " + attribute);
                            Collection result = (Collection) bigguys.get(valueItem.substring(3));
                            Set s = new TreeSet((Collection)elementToAttribute.get(attribute));
                            s.removeAll(result);
                            if (s.size() != 0) {
                                System.out.print("Warning: Missing values for " + element + ", " + attribute + ": ");
                                for (Iterator it = s.iterator(); it.hasNext(); ) {
                                    System.out.print(it.next() + " ");
                                }
                                System.out.println();
                            }
                            return result;
                        }
                        List result = new ArrayList();
                        result.add(valueItem);
                        return result;
                    }
                    return Arrays.asList((Object[])row[2]);
                }
            }
            return (Collection) elementToAttribute.get(attribute);
        }
        int getValueCount(Map elementToAttribute) {
            int result = 0;
            for (Iterator it2 = elementToAttribute.keySet().iterator(); it2.hasNext();) {
                String attribute = (String) it2.next();
                Set valueSet = (Set) elementToAttribute.get(attribute);
                result += valueSet.size();
            }
            return result;
        }
    }

    static TripleData tripleData = new TripleData();

    class Element implements Comparable {
        String elementName;
        SimpleAttributes attributes;
        String comment;

        Element(String elementName, Attributes attributes, String comment) {
            //elementOrdering.add(elementName);
            this.elementName = elementName;
            this.attributes = new SimpleAttributes(attributes, elementName);
            this.comment = comment;
        }
        /**
         * @param attribute
         * @param value
         */
        public void setAttribute(String attribute, String value) {
            attributes.set(elementName, attribute, value);
        }
        /**
         * @param attributeName
         * @return
         */
        public String getValue(String attributeName) {
            return attributes.getValue(attributeName);
        }

        Element(Element other) {
            //elementOrdering.add(elementName);
            this.elementName = other.elementName;
            this.attributes = new SimpleAttributes(other.attributes, elementName);
            this.comment = other.comment;
        }

        public String toString() {
            //throw new IllegalArgumentException("Don't use2");
            return toString(PATH);
        }
        /*
        public String toString(boolean path) {
            return toString(START_VALUE, path);
        }
        */
        static final int PATH = -1, NO_VALUE = 0, START_VALUE = 1, END_VALUE = 2;
        public String toString(int type) {
            String a = attributes.toString(type==PATH, elementName.equals("zone"));
            String result;
            if (type==PATH) {
                //if (type == NO_VALUE) return elementName + a + "-NOVALUE";
                //if (type == END_VALUE) return "END-" + elementName + ">";
                result = elementName + a;
            } else {
                if (type == NO_VALUE) result = "<" + elementName + a + "/>";
                else if (type == END_VALUE) result = "</" + elementName + ">";
                else result = "<" + elementName + a + ">";
            }
            return result;
        }
        public int compareTo(Object o) {
            if (o == null) return 1;
            int result;
            Element that = (Element) o;
            if ((result = elementOrdering.compare(elementName, that.elementName)) != 0) return result;
            return attributes.compareTo(that.attributes);
        }
        public boolean equals(Object o) {
            if (!(o instanceof Element)) return false;
            return compareTo(o) == 0;
        }
        /*
        public void addComment(String in_comment) {
            if (comment == null) comment = in_comment;
            else comment += NEWLINE + in_comment;
            return;
        }
        */
        /**
         * @param ignorelist
         * @returns
         */
        public void removeAttributes(Set ignorelist) {
            attributes.removeAttributes(ignorelist);
        }
    }

    private void writeElementComment(StringBuffer out, String comment, int common) {
        if (comment != null) {
            indent(common, out);
            out.append("<!-- ");
            out.append(comment);
            if (comment.indexOf('\n') >= 0) {
                out.append(Utility.LINE_SEPARATOR);
                indent(common, out);
            } else {
                out.append(" ");
            }
            out.append("-->" + Utility.LINE_SEPARATOR);
        }
    }

    class ElementChain implements Comparable {
        List contexts;

        ElementChain() {
            contexts = new ArrayList();
        }

        /**
         * @param string
         * @return
         */
        public Element getElement(String string) {
            for (int i = 0; i < contexts.size(); ++i) {
                Element x = (Element)contexts.get(i);
                if (string.equals(x.elementName)) return x;
            }
            return null;
        }

        /**
         * @param element
         * @param attribute
         * @param value
         */
        public void setAttribute(String element, String attribute, String value) {
            for (int i = 0; i < contexts.size(); ++i) {
                Element context = (Element)contexts.get(i);
                if (context.elementName.equals(element)) {
                    context = new Element(context); // clone for safety
                    context.attributes.set(element, attribute, value);
                    contexts.set(i, context);
                    break;
                }
            }
        }

        ElementChain(ElementChain other) {
            contexts = new ArrayList(other.contexts);
        }

        public ElementChain push(String elementName, Attributes attributes, String comment) {
            elementOrdering.add(elementName);
            contexts.add(new Element(elementName, attributes, comment));
            return this;
        }

        public void pop(String elementName) {
            int last = contexts.size()-1;
            Element c = (Element) contexts.get(last);
            if (!c.elementName.equals(elementName)) throw new IllegalArgumentException("mismatch");
            contexts.remove(last);
        }

        public String toString() {
            //throw new IllegalArgumentException("Don't use");
            return toString(true, 0, Integer.MAX_VALUE);
        }

        public String toString(boolean path, int startLevel, int limitLevel) {
            StringBuffer buffer = new StringBuffer();
            if (startLevel < 0) startLevel = 0;
            if (limitLevel > contexts.size()) limitLevel = contexts.size();
            for (int i = startLevel; i < limitLevel; ++i) {
                //if (i != 0) buffer.append(' ');
                Element e = (Element) contexts.get(i);
                if (path) buffer.append("/" + e.toString(Element.PATH));
                else buffer.append(e.toString(Element.START_VALUE));
            }
            return buffer.toString();
        }

        public boolean equals(Object other) {
            return compareTo(other) == 0;
        }
        public int hashCode() {
            return contexts.hashCode();
        }

        /* (non-Javadoc)
         * @see java.lang.Comparable#compareTo(java.lang.Object)
         */
        public int compareTo(Object o) {
            int result;
            ElementChain that = (ElementChain) o;
            if (DEBUG_ELEMENT && containsElement("weekendEnd") && that.containsElement(("weekendEnd"))) {
                result = 666;
            }
            int minLen = Math.min(contexts.size(), that.contexts.size());
            for (int i = 0; i < minLen; ++i) {
                if ((result = ((Element)contexts.get(i)).compareTo(that.contexts.get(i))) != 0) return result;
            }
            return compareInt(contexts.size(), that.contexts.size());
        }
        public void writeDifference(ElementChain former, EndNode value, StringBuffer out) {
            // find the identical stuff first!
            int csize = contexts.size();
            int fsize = former.contexts.size();
            int minLen = Math.min(csize, fsize);
            int result;

            // skip stuff that is in common
            int common;
            for (common = 0; common < minLen; ++common) {
                if ((result = ((Element)contexts.get(common)).compareTo(former.contexts.get(common))) != 0) break;
            }
            // finish up old elements, by writing out termination elements.
            // We don't do the very last one, however, since that was done with the value
            for (int j = fsize - 2; j >= common; --j) {
                indent(j, out);
                out.append(((Element)former.contexts.get(j)).toString(Element.END_VALUE));
                out.append(NEWLINE);
            }
            if (csize == 0) return; // we must be at the very end, bail.

            // write new elements if needed.
            for (; common < csize-1; ++common) {
                Element ee = ((Element)contexts.get(common));
                writeElementComment(out, ee.comment, common);
                indent(common, out);
                out.append(ee.toString(Element.START_VALUE));
                out.append(NEWLINE);
            }
            // now write the very current element
            Element ee = ((Element)contexts.get(csize-1));
            writeElementComment(out, ee.comment, common);
            indent(common, out);
            if (value == null || "".equals(value.string)) {
                out.append(ee.toString(Element.NO_VALUE));
            } else if (value.string == null) {
                Element temp = new Element(ee); // clone for safety
                temp.attributes.add(value.attributes);
                out.append(temp.toString(Element.NO_VALUE));
            } else {
                out.append(ee.toString(Element.START_VALUE));
                out.append(value.toString(Element.NO_VALUE));
                out.append(ee.toString(Element.END_VALUE));
            }
            out.append(NEWLINE);
        }

        public boolean containsElement(String string) {
            return getElement(string) != null;
        }

        public Element getLast() {
            return (Element) contexts.get(contexts.size()-1);
        }

        /**
         * @param ignorelist
         * @return
         */
        public ElementChain createRemovingAttributes(Set ignorelist) {
            ElementChain result = new ElementChain(this);
            for (int i = 0; i < contexts.size(); ++i) {
                Element e = (Element)contexts.get(i);
                e.removeAttributes(ignorelist);
            }
            return result;
        }

        /**
         * @param comment
         */
        /*
        public void addComment(String comment) {
            int count = contexts.size();
            if (count == 0) {
                System.out.println("Skipping start comment for now");
                //if (startComment == null) startComment = comment;
                //else startComment += NEWLINE + comment;
                return;
            }
            Element ec = (Element) contexts.get(count-1);
            ec.addComment(comment);
        }
        */
    }

    static int compareInt(int a, int b) {
        return a < b ? -1 : a > b ? 1 : 0;
    }

    static void indent(int count, StringBuffer out) {
        for (int i = 0; i < count; ++i) {
            out.append("\t");
        }
    }

    /*
    static {
        Object[][] temp = {
            {"keys", new Integer(13)},
            {"scripts", new Integer(7)},
            {"script", new Integer(8)},
            {"era", new Integer(38)},
            {"ldml", new Integer(0)},
            {"calendar", new Integer(22)},
            {"numbers", new Integer(57)},
            {"timeFormats", new Integer(44)},
            {"infinity", new Integer(69)},
            {"localizedPatternChars", new Integer(20)},
            {"dateTimeFormats", new Integer(47)},
            {"eraAbbr", new Integer(37)},
            {"exemplarCharacters", new Integer(18)},
            {"month", new Integer(26)},
            {"variants", new Integer(11)},
            {"group", new Integer(60)},
            {"dateTimeFormat", new Integer(49)},
            {"day", new Integer(30)},
            {"zone", new Integer(51)},
            {"types", new Integer(15)},
            {"timeFormat", new Integer(46)},
            {"default", new Integer(40)},
            {"dates", new Integer(19)},
            {"language", new Integer(4)},
            {"long", new Integer(52)},
            {"version", new Integer(2)},
            {"dayWidth", new Integer(29)},
            {"characters", new Integer(17)},
            {"variant", new Integer(12)},
            {"short", new Integer(55)},
            {"generation", new Integer(3)},
            {"am", new Integer(34)},
            {"pattern", new Integer(43)},
            {"minDays", new Integer(32)},
            {"displayName", new Integer(73)},
            {"perMille", new Integer(68)},
            {"monthContext", new Integer(24)},
            {"days", new Integer(27)},
            {"months", new Integer(23)},
            {"territories", new Integer(9)},
            {"identity", new Integer(1)},
            {"currency", new Integer(72)},
            {"exponential", new Integer(67)},
            {"territory", new Integer(10)},
            {"firstDay", new Integer(33)},
            {"languages", new Integer(6)},
            {"nan", new Integer(70)},
            {"week", new Integer(31)},
            {"nativeZeroDigit", new Integer(63)},
            {"decimal", new Integer(59)},
            {"symbols", new Integer(58)},
            {"daylight", new Integer(54)},
            {"calendars", new Integer(21)},
            {"eras", new Integer(36)},
            {"localeDisplayNames", new Integer(5)},
            {"dateTimeFormatLength", new Integer(48)},
            {"dateFormats", new Integer(39)},
            {"exemplarCity", new Integer(56)},
            {"currencies", new Integer(71)},
            {"minusSign", new Integer(66)},
            {"list", new Integer(61)},
            {"dateFormatLength", new Integer(41)},
            {"type", new Integer(16)},
            {"plusSign", new Integer(65)},
            {"dayContext", new Integer(28)},
            {"dateFormat", new Integer(42)},
            {"symbol", new Integer(74)},
            {"timeZoneNames", new Integer(50)},
            {"key", new Integer(14)},
            {"patternDigit", new Integer(64)},
            {"percentSign", new Integer(62)},
            {"standard", new Integer(53)},
            {"monthWidth", new Integer(25)},
            {"pm", new Integer(35)},
            {"timeFormatLength", new Integer(45)},
        };
        elementOrdering = new MapComparator(temp);
    }
    */

    public static class MapComparator {
        Map ordering = new TreeMap(); // maps from name to rank
        List rankToName = new ArrayList();

        MapComparator(){}
        MapComparator(Comparable[] data) {
            for (int i = 0; i < data.length; ++i) {
                add(data[i]);
            }
        }
        public void add(Comparable newObject) {
            Object already = ordering.get(newObject);
            if (already == null) {
                ordering.put(newObject, new Integer(rankToName.size()));
                rankToName.add(newObject);
            }
        }
        public int compare(Comparable a, Comparable b) {
            Comparable aa = (Comparable) ordering.get(a);
            Object bb = ordering.get(b);
            if (aa == null || bb == null) return a.compareTo(b);
            return aa.compareTo(bb);
        }
        public String toString() {
            StringBuffer buffer = new StringBuffer();
            for (Iterator it = rankToName.iterator(); it.hasNext();) {
                Object key = it.next();
                buffer.append("\"").append(key).append("\","+NEWLINE);
            }
            return buffer.toString();
        }
    }

    public static class OrderedMap {
        private Map map = new TreeMap();
        private List list = new ArrayList();
        public void put(Object a, Object b) {
            map.put(a,b);
            list.add(a);
        }
        /**
         * @param key
         */
        public Object getKeyFor(ElementChain key) {
            for (int i = 0; i < list.size(); ++i) {
                if (list.get(i).equals(key)) return key;
            }
            return null;
        }
        /**
         * @param object
         */
        public void remove(Object object) {
            map.remove(object);
            list.remove(object);
        }
         /*
        public void removeAll(OrderedMap other) {
            for (Iterator it = other.iterator(); it.hasNext();) {
                Object key = it.next();
                Object otherValue = other.map.get(key);
                Object value = map.get(key);
                if (value == null || !value.equals(otherValue)) continue;
                if (((ElementChain)key).containsElement("identity")) continue;
                value.equals(otherValue);
                log.println("Removing " + ((ElementChain)key).toString(true, 0) + "\t" + value);
                map.remove(key);
                while(list.remove(key)) {}
            }
        }
        */
        public Object get(Object a) {
            return map.get(a);
        }
        public Iterator iterator() {
            return list.iterator();
        }
        public int size() {
            return list.size();
        }
        public Object get(int index) {
            return list.get(index);
        }
    }

    ElementChain putData(ElementChain stack, String associatedData) {

        //If the associated data is "" and we have a final element in LEAFNODE,
        //then pull off the last element, and make it the associated data

        ElementChain result = new ElementChain(stack);
        EndNode value = new EndNode();
        value.string = associatedData;
        Element lastElement = result.getLast();
        if (LEAFNODES.contains(lastElement.elementName)) {
            if (associatedData.length() != 0) {
                System.err.println("Leaf Node must be empty: " + lastElement + "\tData: " + associatedData);
            }
            value.attributes = lastElement.attributes;
            lastElement.attributes = new SimpleAttributes();
            value.string = null;
        }
        if (DEBUG_SHOW_ADD) System.out.println("Adding: " + result + "\t" + value);
        EndNode alreadyThere = (EndNode) data.get(result);
        if (alreadyThere != null) {
            System.err.println("Overriding: " + result + "\tOld Value: " + alreadyThere + ",\t New Value: " + value);
        }
        data.put(result, value);
        return result;
    }

    //String startComment;

    static SidewaysView sidewaysView = new SidewaysView();

    static class EndNode {
        String string;
        SimpleAttributes attributes;
        public void set(Object associatedData) {
            if (associatedData instanceof String) {
                string = (String) associatedData;
                attributes = null;
            } else {
                attributes = (SimpleAttributes) associatedData;
                string = null;
            }
        }
        public String toString() {
            return toString(Element.PATH);
        }
        public String toString(int type) {
            if (string != null) return TransliteratorUtilities.toHTML.transliterate(string);
            return attributes.toString(); // TO FIX type==Element.PATH
        }
        /*
        public static String showValue(Object value) {
            if (value instanceof String) {
                return BagFormatter.toHTML.transliterate((String)value);
            }
            return ((Element)value).toString();
        }
        */


    }

    static class EndNodeComparator implements Comparator {
        public int compare(Object o1, Object o2) {
            EndNode these = (EndNode) o1;
                EndNode that = (EndNode) o2;
                if (these.string != null) {
                    if (that.string != null) {
                        return getDefaultCollation().compare(these.string, that.string);
                    }
                    return -1;
                } else if (that.attributes != null) {
                    return these.attributes.compareTo(that.attributes);
                }
                return 1;
            }
    }

    static class SidewaysView {
        EndNodeComparator enc = new EndNodeComparator();
        Map contextCache = new TreeMap();
        Set fileNames = new TreeSet();
        Set allTypes = new TreeSet();

        void putData(OrderedMap data, String filename) {
            for (Iterator it = data.iterator(); it.hasNext();) {
                ElementChain original = (ElementChain) it.next();
                ElementChain copy = original.createRemovingAttributes(IGNORELIST);
                EndNode endNode = (EndNode)data.get(copy);
                Map dataToFile = (Map)contextCache.get(copy);
                if (dataToFile == null) {
                    dataToFile = new TreeMap(enc);
                    contextCache.put(copy, dataToFile);
                 }
                //System.out.println(copy + "\t\t" + endNode);
                Set files = (Set) dataToFile.get(endNode);
                if (files == null) {
                    files = new TreeSet();
                    dataToFile.put(endNode, files);
                }
                files.add(filename);
            }
            if (filename.indexOf('_') < 0
                || filename.equals("zh_Hant")) fileNames.add(filename); // add all language-only locales
        }

        int getChainDepth(ElementChain ec) {
            Element e = (Element)ec.contexts.get(1);
            String result = e.elementName;
            if (result.equals("numbers") || result.equals("localeDisplayNames") || result.equals("dates")) {
                return 3;
            }
            return 2;
        }

        String getChainName(ElementChain ec, int limit) {
            Element e = (Element)ec.contexts.get(1);
            String result = e.elementName;
            for (int i = 2; i < limit; ++i) {
                e = (Element)ec.contexts.get(i);
                result += "_" + e.elementName;
            }
            return result;
        }
        /*
        String getLastElementsType(ElementChain ec) {
            //TODO make SimpleAttributes a map instead of a set.
            Element e = (Element)ec.contexts.get(ec.contexts.size()-1);
            SimpleAttributes sa = e.attributes;
            for (Iterator it = sa.contents.iterator(); it.hasNext();) {
                SimpleAttribute sa1 = (SimpleAttribute)it.next();
                if (sa1.name.equals("type")) return sa1.value;
            }
            return "*NOT_FOUND*";
        }
        */
        void showCacheData() throws IOException {
        	UnicodeSet untransliteratedCharacters = new UnicodeSet();
        	Set translitErrors = new TreeSet();
        	GenerateCldrTests.DraftChecker dc = new GenerateCldrTests.DraftChecker(options[SOURCEDIR].value);
        	dc.isDraft("en");
            writeStyleSheet();
            PrintWriter out = null;
            String lastChainName = "";
            int lineCounter = 1;
            for (Iterator it = contextCache.keySet().iterator(); it.hasNext();) {
                ElementChain stack = (ElementChain) it.next();
                int limit = getChainDepth(stack);
                String chainName = getChainName(stack, limit);
                if (!chainName.equals(lastChainName)) {
                    if (out != null) {
                        out.println("</table>");
                        writeFooterAndClose(out);
                    }
                    allTypes.add(chainName); // add to the list
                    out = openAndDoHeader(chainName);
                    lastChainName = chainName;
                    lineCounter = 0;
                }
                String key = stack.toString(true, limit, Integer.MAX_VALUE);
                // strip    /ldml@version="1.2"/;

                lineCounter++;
                out.println("<tr><td colspan='2' class='head'>" +
                        "<a href='#" + lineCounter + "' name='" + lineCounter + "'>"
                        + lineCounter + "</a>&nbsp;" +
                        TransliteratorUtilities.toHTML.transliterate(key) + "</td></tr>");
                Map dataToFile = (Map) contextCache.get(stack);
                // walk through once, and gather all the filenames
                Set remainingFiles = new TreeSet(fileNames);
                for (Iterator it2 = dataToFile.keySet().iterator(); it2.hasNext();) {
                    Object data = it2.next();
                    remainingFiles.removeAll((Set) dataToFile.get(data));
                }
                // hack for zh_Hant
                if (!remainingFiles.contains("zh")) remainingFiles.remove("zh_Hant");
                // now display
                for (Iterator it2 = dataToFile.keySet().iterator(); it2.hasNext();) {
                    EndNode data = (EndNode) it2.next();
                    String dataStyle = "";
                    Set files = (Set) dataToFile.get(data);
                    if (files.contains("root")) {
                        files.addAll(remainingFiles);
                        dataStyle = " class='nodata'";
                    }

                    String extra = "";
                    if (data.string != null && options[TRANSLIT].doesOccur 
                    		&& GenerateCldrTests.NON_LATIN.containsSome(data.string)) {                    	
                    	try {
							extra = GenerateCldrTests.toLatin.transliterate(data.string);
	                    	untransliteratedCharacters.addAll(extra);
	                    	if (extra.equals(data.string)) extra = "";
	                  		else extra = "<br>(\"" + TransliteratorUtilities.toHTML.transliterate(extra) + "\")";                      	
						} catch (RuntimeException e) {
							translitErrors.add(e.getMessage());
						}
                    }
                    out.print("<tr><th" + dataStyle + 
                            (lineCounter == 1 ? " width='20%'" : "")
                            + ">\"" + data + "\""
							+ extra
							+ "</th><td>");
                    boolean first = true;
                    for (Iterator it3 = files.iterator(); it3.hasNext();) {
                        if (first) first = false;
                        else out.print(" ");
                        String localeID = (String)it3.next();
                        boolean emphasize = localeID.equals("root") || localeID.indexOf('_') >= 0;
                        if (dc.isDraft(localeID)) out.print("<i>");
                        if (emphasize) out.print("<b>");
                        out.print("\u00B7" + localeID + "\u00B7");
                        if (emphasize) out.print("</b>");
                        if (dc.isDraft(localeID)) out.print("</i>");
                    }
                    out.println("</td></tr>");
                }
            }
            if(out==null) {
                System.err.println("Out = null?");
            } else {
                out.println("</table>");
                writeFooterAndClose(out);
                out.close();
            }
            writeIndex();
            tripleData.writeData();
            untransliteratedCharacters.retainAll(GenerateCldrTests.NON_LATIN);
            log.println("Untranslated Characters*: " + untransliteratedCharacters.toPattern(false));
            log.println("Untranslated Characters* (hex): " + untransliteratedCharacters.toPattern(true));
            untransliteratedCharacters.closeOver(UnicodeSet.CASE);
            log.println("Untranslated Characters: " + untransliteratedCharacters.toPattern(false));
            log.println("Untranslated Characters (hex): " + untransliteratedCharacters.toPattern(true));
            for (Iterator it = translitErrors.iterator(); it.hasNext();) {
            	log.println(it.next());
            }
        }

        /**
         * @param type
         * @return
         * @throws IOException
         */
        private PrintWriter openAndDoHeader(String type) throws IOException {
            String fileName = type + ".html";
            PrintWriter out = BagFormatter.openUTF8Writer(options[DESTDIR].value, "by_type" + File.separator + fileName);
            out.println("<html><head>");
            out.println("<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>");
            out.println("<title>Comparison By Type: " + TransliteratorUtilities.toHTML.transliterate(type) + "</title>");
            out.println("<link rel='stylesheet' type='text/css' href='http://oss.software.ibm.com/cvs/icu/~checkout~/icuhtml/common.css'>");
            out.println("<link rel='stylesheet' type='text/css' href='by_type.css'>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h2>By-Type Chart for " + "//ldml/" + type.replace('_', '/') + "/...</h1>");
            out.println("<p>" +
                    "<a href=\"index.html\">By-Type Chart Index</a> " +
                    "| <a href='http://www.jtcsv.com/cgibin/cldrwiki.pl?InterimVettingCharts'>Interim Vetting Charts</a>" +
                    "| <a href='http://oss.software.ibm.com/cvs/icu/~checkout~/locale/docs/tr35.html'>LDML Specification</a>" +
                    "| <a href='http://www.unicode.org/cldr/filing_bug_reports.html'>Filing Bug Reports</a>" +
                    "| <a href='http://oss.software.ibm.com/cvs/icu/~checkout~/locale/comparison_charts.html'>Cross Platform Charts</a>" +
                    "</p>");
            out.println("<p>This chart shows values across locales for different fields. " +
                    "Each value is listed under the field designator (in XML XPath format), " +
                    "followed by all the locales that use it. " +
                    "Locales are omitted if the value would be the same as the parent's. " +
					"The locales are listed in the format: \u00B7aa\u00B7 for searching. " +
                    "The value appears in red if it is the same as the root. " +
                    "Draft locales are italic-gray; territory locales are bold.</p>");             
            out.println("<table>");
            return out;
        }
        private void writeStyleSheet() throws IOException {
            PrintWriter out = BagFormatter.openUTF8Writer(options[DESTDIR].value, "by_type" + File.separator + "by_type.css");
            out.println(".head { font-weight:bold; background-color:#DDDDFF }");
            out.println("td, th { border: 1px solid #0000FF; text-align }");
            out.println("th { width:10% }");
            out.println("i { color: gray }");            
            out.println(".nodata { background-color:#FF0000 }");
            out.println("table {margin-top: 1em}");
            out.close();
        }

        private void writeIndex() throws IOException {
            String fileName = "index" + ".html";
            PrintWriter out = BagFormatter.openUTF8Writer(options[DESTDIR].value, "by_type" + File.separator + fileName);
            out.println("<html><head>");
            out.println("<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>");
            out.println("<title>Comparison By Type: " + "index" + "</title>");
            out.println("<link rel='stylesheet' type='text/css' href='http://oss.software.ibm.com/cvs/icu/~checkout~/icuhtml/common.css'>");
            out.println("<link rel='stylesheet' type='text/css' href='by_type.css'>");
            out.println("</head>");
            out.println("<body><h1>By Type Chart Index</h1>");
            out.println("<p>The following are charts for the individual datatypes, " +
                    "that show a comparison across locales for different fields. " +
                    "For example, in the orientation chart, one can see that all locales " +
                    "are left-to-right except \u00B7ar\u00B7 \u00B7fa\u00B7 \u00B7he\u00B7 \u00B7ps\u00B7 (and their children).</p>" +
                    "<p>Note: these charts do not yet include collations</p>");
            out.println("<ul>");
            for(Iterator e = allTypes.iterator();e.hasNext();) {
                String f = (String)e.next();
                out.println(" <li><a href=\"" + f + ".html" +  "\">" + f + "</a>");
            }
            out.println("</ul>");
            out.println("<a href='http://www.jtcsv.com/cgibin/cldrwiki.pl?ByType'>About this chart</a> <br/>");
            writeFooterAndClose(out);
        }
    }

    class MyDeclHandler implements DeclHandler {
        Map element_childComparator = new TreeMap();
        boolean showReason = false;
        Set SKIP_LIST = new HashSet(Arrays.asList(new String[] {
                "collation", "base", "settings", "suppress_contractions", "optimize", "rules", "reset",
                "context", "p", "pc", "s", "sc", "t", "tc", "q", "qc", "i", "ic", "extend", "x"
        }));
        Object DONE = new Object(); // marker

        public void checkData() {
            // verify that the ordering is the consistent for all child elements
            // do this by building an ordering from the lists.
            // The first item has no greater item in any set. So find an item that is only first
            showReason = false;
            List orderingList = new ArrayList();
            while (true) {
                Object first = getFirst(orderingList);
                if (first == DONE) {
                    log.println("Successful Ordering");
                    int count = 0;
                    for (Iterator it = orderingList.iterator(); it.hasNext();) log.println(++count + it.next().toString());
                    break;
                }
                if (first != null) {
                    orderingList.add(first);
                } else {
                    showReason = true;
                    getFirst(orderingList);
                    log.println();
                    log.println("Failed ordering. So far:");
                    for (Iterator it = orderingList.iterator(); it.hasNext();) log.print("\t" + it.next());
                    log.println();
                    log.println("Items:");
                    for (Iterator it =  element_childComparator.keySet().iterator(); it.hasNext();) showRow(it.next(), true);
                    log.println();
                    break;
                }
            }
        }

        /**
         * @param parent
         * @param skipEmpty TODO
         */
        private void showRow(Object parent, boolean skipEmpty) {
            List items = (List) element_childComparator.get(parent);
            if (skipEmpty && items.size() == 0) return;
            log.print(parent);
            for (Iterator it2 = items.iterator(); it2.hasNext();) log.print("\t" + it2.next());
            log.println();
        }

        /**
         * @param orderingList
         */
        private Object getFirst(List orderingList) {
            Set keys = element_childComparator.keySet();
            Set failures = new HashSet();
            boolean allZero = true;
            for (Iterator it = keys.iterator(); it.hasNext();) {
                List list = (List) element_childComparator.get(it.next());
                if (list.size() != 0) {
                    allZero = false;
                    Object possibleFirst = list.get(0);
                    if (!failures.contains(possibleFirst) && isAlwaysFirst(possibleFirst)) {
                        // we survived the guantlet. add to ordering list, remove from the mappings
                        removeEverywhere(possibleFirst);
                        return possibleFirst;
                    } else {
                        failures.add(possibleFirst);
                    }
                }
            }
            if (allZero) return DONE;
            return null;
        }
        /**
         * @param possibleFirst
         */
        private void removeEverywhere(Object possibleFirst) {
            // and remove from all the lists
            for (Iterator it2 = element_childComparator.keySet().iterator(); it2.hasNext();) {
                List list2 = (List) element_childComparator.get(it2.next());
                list2.remove(possibleFirst);
            }
        }

        private boolean isAlwaysFirst(Object possibleFirst) {
            if (showReason) log.println("Trying: " + possibleFirst);
            for (Iterator it2 = element_childComparator.keySet().iterator(); it2.hasNext();) {
                Object key = it2.next();
                List list2 = (List) element_childComparator.get(key);
                int pos = list2.indexOf(possibleFirst);
                if (pos > 0) {
                    if (showReason) {
                        log.print("Failed at:\t");
                        showRow(key, false);
                    }
                    return false;
                }
            }
            return true;
        }

        // refine later; right now, doesn't handle multiple elements well.
        public void elementDecl(String name, String model) throws SAXException {
            if (SKIP_LIST.contains(name)) return;
            //log.println("Element\t" + name + "\t" + model);
            String[] list = model.split("[^A-Z0-9a-z]");
            List mc = new ArrayList();
            if (name.equals("currency")) {
                mc.add("alias");
                mc.add("symbol");
                mc.add("pattern");
            }
            for (int i = 0; i < list.length; ++i) {
                if (list[i].length() == 0) continue;
                //log.print("\t" + list[i]);
                if (mc.contains(list[i])) {
                    log.println("Duplicate attribute " + name + ", " + list[i]);
                } else {
                    mc.add(list[i]);
                }
            }
            element_childComparator.put(name, mc);
            //log.println();
        }
        public void attributeDecl(String eName, String aName, String type, String mode, String value) throws SAXException {
            //log.println("Attribute\t" + eName + "\t" + aName + "\t" + type + "\t" + mode + "\t" + value);
        }
        public void internalEntityDecl(String name, String value) throws SAXException {
            //log.println("Internal Entity\t" + name + "\t" + value);
        }
        public void externalEntityDecl(String name, String publicId, String systemId) throws SAXException {
            //log.println("Internal Entity\t" + name + "\t" + publicId + "\t" + systemId);
        }

    }

    class MyContentHandler implements ContentHandler, LexicalHandler {

        ElementChain contextStack = new ElementChain();
        String lastChars = "";
        boolean justPopped = false;
        int commentStack = 0;

        public void startElement(
            String uri,
            String localName,
            String qName,
            Attributes attributes)
            throws SAXException {
                //data.put(new ContextStack(contextStack), lastChars);
                //lastChars = "";
                try {
                    contextStack.push(qName, attributes, finalComment);
                    finalComment = null;
                    if (DEBUG) System.out.println("startElement:\t" + contextStack);
                    justPopped = false;
                } catch (RuntimeException e) {
                    e.printStackTrace();
                    throw e;
                }
        }
        public void endElement(String uri, String localName, String qName)
            throws SAXException {
                try {
                    if (DEBUG) System.out.println("endElement:\t" + contextStack);
                    if (lastChars.length() != 0 || justPopped == false) {
                        putData(contextStack, lastChars);
                        lastChars = "";
                    }
                    contextStack.pop(qName);
                    justPopped = true;
                } catch (RuntimeException e) {
                    e.printStackTrace();
                    throw e;
                }
            }
        public void characters(char[] ch, int start, int length)
            throws SAXException {
                try {
                    String value = new String(ch,start,length);
                    if (DEBUG) System.out.println("characters:\t" + value);
                    lastChars += value;
                    justPopped = false;
                } catch (RuntimeException e) {
                    e.printStackTrace();
                    throw e;
                }
            }

        // just for debugging

        public void notationDecl (String name, String publicId, String systemId)
        throws SAXException {
            if (DEBUG2) System.out.println("notationDecl: " + name
            + ", " + publicId
            + ", " + systemId
            );
        }

        public void processingInstruction (String target, String data)
        throws SAXException {
            if (DEBUG2) System.out.println("processingInstruction: " + target + ", " + data);
        }

        public void skippedEntity (String name)
        throws SAXException
        {
            if (DEBUG2) System.out.println("skippedEntity: " + name
            );
        }

        public void unparsedEntityDecl (String name, String publicId,
                        String systemId, String notationName) {
            if (DEBUG2) System.out.println("unparsedEntityDecl: " + name
            + ", " + publicId
            + ", " + systemId
            + ", " + notationName
            );
        }
        public void setDocumentLocator(Locator locator) {
            if (DEBUG2) System.out.println("setDocumentLocator Locator " + locator);
        }
        public void startDocument() throws SAXException {
            if (DEBUG2) System.out.println("startDocument");
            commentStack = 0; // initialize
        }
        public void endDocument() throws SAXException {
            if (DEBUG2) System.out.println("endDocument");
        }
        public void startPrefixMapping(String prefix, String uri) throws SAXException {
            if (DEBUG2) System.out.println("startPrefixMapping prefix: " + prefix +
                    ", uri: " + uri);
        }
        public void endPrefixMapping(String prefix) throws SAXException {
            if (DEBUG2) System.out.println("endPrefixMapping prefix: " + prefix);
        }
        public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
            if (DEBUG2) System.out.println("ignorableWhitespace length: " + length);
        }
        public void startDTD(String name, String publicId, String systemId) throws SAXException {
            if (DEBUG2) System.out.println("startDTD name: " + name
                    + ", publicId: " + publicId
                    + ", systemId: " + systemId
            );
            commentStack++;
        }
        public void endDTD() throws SAXException {
            if (DEBUG2) System.out.println("endDTD");
            commentStack--;
        }
        public void startEntity(String name) throws SAXException {
            if (DEBUG2) System.out.println("startEntity name: " + name);
        }
        public void endEntity(String name) throws SAXException {
            if (DEBUG2) System.out.println("endEntity name: " + name);
        }
        public void startCDATA() throws SAXException {
            if (DEBUG2) System.out.println("startCDATA");
        }
        public void endCDATA() throws SAXException {
            if (DEBUG2) System.out.println("endCDATA");
        }
        public void comment(char[] ch, int start, int length) throws SAXException {
            if (commentStack != 0) return;
            String comment = new String(ch, start,length).trim();
            if (finalComment == null) finalComment = comment;
            else finalComment += NEWLINE + comment;
            if (DEBUG2) System.out.println("comment: " + comment);
        }
    }

    String finalComment = null;

    XMLReader createXMLReader(boolean validating) {
        XMLReader result = null;
        try { // Xerces
            result =  XMLReaderFactory
                    .createXMLReader("org.apache.xerces.parsers.SAXParser");
            result.setFeature("http://xml.org/sax/features/validation", validating);
        } catch (SAXException e1) {
            try { // Crimson
                result =  XMLReaderFactory
                        .createXMLReader("org.apache.crimson.parser.XMLReaderImpl");
                result.setFeature("http://xml.org/sax/features/validation", validating);
            } catch (SAXException e2) {
                try { // AElfred
                    result =  XMLReaderFactory
                            .createXMLReader("gnu.xml.aelfred2.XmlReader");
                    result.setFeature("http://xml.org/sax/features/validation", validating);
                } catch (SAXException e3) {
                    try { // Piccolo
                        result =  XMLReaderFactory
                                .createXMLReader("com.bluecast.xml.Piccolo");
                        result.setFeature("http://xml.org/sax/features/validation", validating);
                    } catch (SAXException e4) {
                        try { // Oracle
                            result =  XMLReaderFactory
                                    .createXMLReader("oracle.xml.parser.v2.SAXParser");
                            result.setFeature("http://xml.org/sax/features/validation", validating);
                        } catch (SAXException e5) {
                            try { // default
                                result =  XMLReaderFactory.createXMLReader();
                                result.setFeature("http://xml.org/sax/features/validation", validating);
                            } catch (SAXException e6) {
                                throw new NoClassDefFoundError(
                                        "No SAX parser is available, or unable to set validation correctly");
                                // or whatever exception your method is
                                // declared to throw
                            }
                        }
                    }
                }
            }
        }
        try {
            result.setEntityResolver(new CachingEntityResolver());
        } catch (Throwable e) {
            System.out
                    .println("WARNING: Can't set caching entity resolver  -  error "
                            + e.toString());
            e.printStackTrace();
        }
        return result;
    }
    private static void writeFooterAndClose(PrintWriter out)
    {
        out.println("Generated " + java.util.Calendar.getInstance().getTime());
        out.println("</body></html>");
        out.close();
    }
}
