• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.unicode.cldr.util;
2 
3 import java.util.Arrays;
4 import java.util.LinkedHashSet;
5 import java.util.Locale;
6 import java.util.Map;
7 import java.util.Set;
8 import java.util.TreeMap;
9 import java.util.TreeSet;
10 
11 import com.google.common.collect.ImmutableMap;
12 import com.ibm.icu.impl.Relation;
13 import com.ibm.icu.impl.Utility;
14 
15 public class IsoCurrencyParser {
16 
17     /**
18      * Note: path is relative to CldrUtility, {@link CldrUtility#getInputStream(String)}
19      */
20     private static final String ISO_CURRENT_CODES_XML = "dl_iso_table_a1.xml";
21 
22     /*
23      * IsoCurrencyParser doesn't currently use the historic codes list, but it could easily be modified/extended to do
24      * so if we need to at some point. (JCE)
25      * private static final String ISO_HISTORIC_CODES_XML = "dl_iso_tables_a3.xml";
26      */
27 
28     /*
29      * CLDR_EXTENSIONS_XML is stuff that would/should be in ISO, but that we KNOW for a fact to be correct.
30      * Some subterritory designations that we use in CLDR, like Ascension Island or Tristan da Cunha aren't
31      * used in ISO4217, so we use an extensions data file to allow our tests to validate the CLDR data properly.
32      */
33     private static final String CLDR_EXTENSIONS_XML = "dl_cldr_extensions.xml";
34 
35     /*
36      * These corrections are country descriptions that are in the ISO4217 tables but carry a different spelling
37      * in the language subtag registry.
38      */
39     private static final ImmutableMap<String, String> COUNTRY_CORRECTIONS = new ImmutableMap.Builder<String, String>()
40         .put("UNITED ARAB EMIRATES (THE)", "AE")
41         .put(Utility.unescape("\u00C5LAND ISLANDS"), "AX")
42         .put("SAINT BARTH\u00C9LEMY", "BL")
43         .put("BOLIVIA (PLURINATIONAL STATE OF)", "BO")
44         .put("BAHAMAS (THE)", "BS")
45         .put("COCOS (KEELING) ISLANDS (THE)", "CC")
46         .put("CONGO (THE DEMOCRATIC REPUBLIC OF THE)", "CD")
47         .put("CENTRAL AFRICAN REPUBLIC (THE)", "CF")
48         .put("CONGO (THE)", "CG")
49         .put(Utility.unescape("C\u00D4TE D\u2019IVOIRE"), "CI")
50         .put("COOK ISLANDS (THE)", "CK")
51         .put("CABO VERDE", "CV")
52         .put(Utility.unescape("CURA\u00C7AO"), "CW")
53         .put("CZECHIA", "CZ")
54         .put("DOMINICAN REPUBLIC (THE)", "DO")
55         .put("FALKLAND ISLANDS (THE) [MALVINAS]", "FK")
56         .put("MICRONESIA (FEDERATED STATES OF)", "FM")
57         .put("FAROE ISLANDS (THE)", "FO")
58         .put("UNITED KINGDOM OF GREAT BRITAIN AND NORTHERN IRELAND (THE)", "GB")
59         .put("GAMBIA (THE)", "GM")
60         .put("HEARD ISLAND AND McDONALD ISLANDS", "HM")
61         .put("BRITISH INDIAN OCEAN TERRITORY (THE)", "IO")
62         .put("IRAN (ISLAMIC REPUBLIC OF)", "IR")
63         .put("COMOROS (THE)", "KM")
64         .put(Utility.unescape("KOREA (THE DEMOCRATIC PEOPLE\u2019S REPUBLIC OF)"), "KP")
65         .put("KOREA (THE REPUBLIC OF)", "KR")
66         .put("CAYMAN ISLANDS (THE)", "KY")
67         .put(Utility.unescape("LAO PEOPLE\u2019S DEMOCRATIC REPUBLIC (THE)"), "LA")
68         .put("MOLDOVA (THE REPUBLIC OF)", "MD")
69         .put("SAINT MARTIN", "MF")
70         .put("MARSHALL ISLANDS (THE)", "MH")
71         .put("MACEDONIA (THE FORMER YUGOSLAV REPUBLIC OF)", "MK")
72         .put("NORTHERN MARIANA ISLANDS (THE)", "MP")
73         .put("NETHERLANDS (THE)", "NL")
74         .put("NIGER (THE)", "NE")
75         .put("PHILIPPINES (THE)", "PH")
76         .put("PALESTINE, STATE OF", "PS")
77         .put(Utility.unescape("R\u00C9UNION"), "RE")
78         .put("RUSSIAN FEDERATION (THE)", "RU")
79         .put("SUDAN (THE)", "SD")
80         .put("ESWATINI", "SZ")
81         .put("TURKS AND CAICOS ISLANDS (THE)", "TC")
82         .put("FRENCH SOUTHERN TERRITORIES (THE)", "TF")
83         .put("TAIWAN (PROVINCE OF CHINA)", "TW")
84         .put("TANZANIA, UNITED REPUBLIC OF", "TZ")
85         .put("UNITED STATES MINOR OUTLYING ISLANDS (THE)", "UM")
86         .put("UNITED STATES OF AMERICA (THE)", "US")
87         .put("HOLY SEE (THE)", "VA")
88         .put("VENEZUELA (BOLIVARIAN REPUBLIC OF)", "VE")
89         .put("VIRGIN ISLANDS (BRITISH)", "VG")
90         .put("VIRGIN ISLANDS (U.S.)", "VI")
91         .put(Utility.unescape("INTERNATIONAL MONETARY FUND (IMF)\u00A0"), "ZZ")
92         .put("MEMBER COUNTRIES OF THE AFRICAN DEVELOPMENT BANK GROUP", "ZZ")
93         .put("SISTEMA UNITARIO DE COMPENSACION REGIONAL DE PAGOS \"SUCRE\"", "ZZ")
94         .put("EUROPEAN MONETARY CO-OPERATION FUND (EMCF)", "ZZ")
95         .build();
96 
97     static Map<String, String> iso4217CountryToCountryCode = new TreeMap<String, String>();
98     static Set<String> exceptionList = new LinkedHashSet<String>();
99     static {
100         StandardCodes sc = StandardCodes.make();
101         Set<String> countries = sc.getAvailableCodes("territory");
102         for (String country : countries) {
103             String name = sc.getData("territory", country);
name.toUpperCase(Locale.ENGLISH)104             iso4217CountryToCountryCode.put(name.toUpperCase(Locale.ENGLISH), country);
105         }
106         iso4217CountryToCountryCode.putAll(COUNTRY_CORRECTIONS);
107     }
108 
109     private Relation<String, Data> codeList = Relation.of(new TreeMap<String, Set<Data>>(), TreeSet.class, null);
110     private Relation<String, String> countryToCodes = Relation.of(new TreeMap<String, Set<String>>(), TreeSet.class, null);
111 
112     public static class Data implements Comparable<Object> {
113         private String name;
114         private String countryCode;
115         private int numericCode;
116         private int minor_unit;
117 
Data(String countryCode, String name, int numericCode, int minor_unit)118         public Data(String countryCode, String name, int numericCode, int minor_unit) {
119             this.countryCode = countryCode;
120             this.name = name;
121             this.numericCode = numericCode;
122             this.minor_unit = minor_unit;
123         }
124 
getCountryCode()125         public String getCountryCode() {
126             return countryCode;
127         }
128 
getName()129         public String getName() {
130             return name;
131         }
132 
getNumericCode()133         public int getNumericCode() {
134             return numericCode;
135         }
136 
getMinorUnit()137         public int getMinorUnit() {
138             return minor_unit;
139         }
140 
toString()141         public String toString() {
142             return String.format("[%s,\t%s [%s],\t%d]", name, countryCode,
143                 StandardCodes.make().getData("territory", countryCode), numericCode);
144         }
145 
compareTo(Object o)146         public int compareTo(Object o) {
147             Data other = (Data) o;
148             int result;
149             if (0 != (result = countryCode.compareTo(other.countryCode))) return result;
150             if (0 != (result = name.compareTo(other.name))) return result;
151             return numericCode - other.numericCode;
152         }
153     }
154 
155     private static IsoCurrencyParser INSTANCE_WITHOUT_EXTENSIONS = new IsoCurrencyParser(false);
156     private static IsoCurrencyParser INSTANCE_WITH_EXTENSIONS = new IsoCurrencyParser(true);
157 
getInstance(boolean useCLDRExtensions)158     public static IsoCurrencyParser getInstance(boolean useCLDRExtensions) {
159         return useCLDRExtensions ? INSTANCE_WITH_EXTENSIONS : INSTANCE_WITHOUT_EXTENSIONS;
160     }
161 
getInstance()162     public static IsoCurrencyParser getInstance() {
163         return getInstance(true);
164     }
165 
getCodeList()166     public Relation<String, Data> getCodeList() {
167         return codeList;
168     }
169 
IsoCurrencyParser(boolean useCLDRExtensions)170     private IsoCurrencyParser(boolean useCLDRExtensions) {
171 
172         ISOCurrencyHandler isoCurrentHandler = new ISOCurrencyHandler();
173         XMLFileReader xfr = new XMLFileReader().setHandler(isoCurrentHandler);
174         xfr.readCLDRResource(ISO_CURRENT_CODES_XML, -1, false);
175         if (useCLDRExtensions) {
176             xfr.readCLDRResource(CLDR_EXTENSIONS_XML, -1, false);
177         }
178         if (exceptionList.size() != 0) {
179             throw new IllegalArgumentException(exceptionList.toString());
180         }
181         codeList.freeze();
182         countryToCodes.freeze();
183     }
184 
185     /*
186      * private Relation<String,Data> codeList = new Relation(new TreeMap(), TreeSet.class, null);
187      * private String version;
188      */
189 
getCountryToCodes()190     public Relation<String, String> getCountryToCodes() {
191         return countryToCodes;
192     }
193 
getCountryCode(String iso4217Country)194     public static String getCountryCode(String iso4217Country) {
195         iso4217Country = iso4217Country.trim();
196         if (iso4217Country.startsWith("\"")) {
197             iso4217Country = iso4217Country.substring(1, iso4217Country.length() - 1);
198         }
199         String name = iso4217CountryToCountryCode.get(iso4217Country);
200         if (name != null) return name;
201         if (iso4217Country.startsWith("ZZ")) {
202             return "ZZ";
203         }
204         exceptionList.add(String.format(CldrUtility.LINE_SEPARATOR + "\t\t.put(\"%s\", \"XXX\") // fix XXX and add to COUNTRY_CORRECTIONS in "
205             + StackTracker.currentElement(0).getFileName(), iso4217Country));
206         return "ZZ";
207     }
208 
209     public class ISOCurrencyHandler extends XMLFileReader.SimpleHandler {
210 
211         // This Set represents the entries in ISO4217 which we know to be bad. I have sent e-mail
212         // to the ISO 4217 Maintenance agency attempting to get them removed. Once that happens,
213         // we can remove these as well.
214         // SVC - El Salvador Colon - not used anymore ( uses USD instead )
215         // ZWL - Last Zimbabwe Dollar - abandoned due to hyper-inflation.
216         Set<String> KNOWN_BAD_ISO_DATA_CODES = new TreeSet<String>(Arrays.asList("SVC", "ZWL"));
217         XPathParts parts = new XPathParts();
218         String country_code;
219         String currency_name;
220         String alphabetic_code;
221         int numeric_code;
222         int minor_unit;
223 
224         /**
225          * Finish processing anything left hanging in the file.
226          */
cleanup()227         public void cleanup() {
228         }
229 
handlePathValue(String path, String value)230         public void handlePathValue(String path, String value) {
231             try {
232                 parts.set(path);
233                 String type = parts.getElement(-1);
234                 if (type.equals("CtryNm")) {
235                     value = value.replaceAll("\n", "");
236                     country_code = getCountryCode(value);
237                     if (country_code == null) {
238                         country_code = "ZZ";
239                     }
240                     alphabetic_code = "XXX";
241                     numeric_code = -1;
242                     minor_unit = 0;
243                 } else if (type.equals("CcyNm")) {
244                     currency_name = value;
245                 } else if (type.equals("Ccy")) {
246                     alphabetic_code = value;
247                 } else if (type.equals("CcyNbr")) {
248                     try {
249                         numeric_code = Integer.valueOf(value);
250                     } catch (NumberFormatException ex) {
251                         numeric_code = -1;
252                     }
253                 } else if (type.equals("CcyMnrUnts")) {
254                     try {
255                         minor_unit = Integer.valueOf(value);
256                     } catch (NumberFormatException ex) {
257                         minor_unit = 2;
258                     }
259                 }
260 
261                 if (type.equals("CcyMnrUnts") && alphabetic_code.length() > 0
262                     && !KNOWN_BAD_ISO_DATA_CODES.contains(alphabetic_code)) {
263                     Data data = new Data(country_code, currency_name, numeric_code, minor_unit);
264                     codeList.put(alphabetic_code, data);
265                     countryToCodes.put(data.getCountryCode(), alphabetic_code);
266                 }
267 
268             } catch (Exception e) {
269                 throw (IllegalArgumentException) new IllegalArgumentException("path: "
270                     + path + ",\tvalue: " + value).initCause(e);
271             }
272         }
273     }
274 }
275