• 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         .put("TÜRKİYE", "TR")
96         .build();
97 
98     static Map<String, String> iso4217CountryToCountryCode = new TreeMap<>();
99     static Set<String> exceptionList = new LinkedHashSet<>();
100     static {
101         StandardCodes sc = StandardCodes.make();
102         Set<String> countries = sc.getAvailableCodes("territory");
103         for (String country : countries) {
104             String name = sc.getData("territory", country);
name.toUpperCase(Locale.ENGLISH)105             iso4217CountryToCountryCode.put(name.toUpperCase(Locale.ENGLISH), country);
106         }
107         iso4217CountryToCountryCode.putAll(COUNTRY_CORRECTIONS);
108     }
109 
110     private Relation<String, Data> codeList = Relation.of(new TreeMap<String, Set<Data>>(), TreeSet.class, null);
111     private Relation<String, String> countryToCodes = Relation.of(new TreeMap<String, Set<String>>(), TreeSet.class, null);
112 
113     public static class Data implements Comparable<Object> {
114         private String name;
115         private String countryCode;
116         private int numericCode;
117         private int minor_unit;
118 
Data(String countryCode, String name, int numericCode, int minor_unit)119         public Data(String countryCode, String name, int numericCode, int minor_unit) {
120             this.countryCode = countryCode;
121             this.name = name;
122             this.numericCode = numericCode;
123             this.minor_unit = minor_unit;
124         }
125 
getCountryCode()126         public String getCountryCode() {
127             return countryCode;
128         }
129 
getName()130         public String getName() {
131             return name;
132         }
133 
getNumericCode()134         public int getNumericCode() {
135             return numericCode;
136         }
137 
getMinorUnit()138         public int getMinorUnit() {
139             return minor_unit;
140         }
141 
142         @Override
toString()143         public String toString() {
144             return String.format("[%s,\t%s [%s],\t%d]", name, countryCode,
145                 StandardCodes.make().getData("territory", countryCode), numericCode);
146         }
147 
148         @Override
compareTo(Object o)149         public int compareTo(Object o) {
150             Data other = (Data) o;
151             int result;
152             if (0 != (result = countryCode.compareTo(other.countryCode))) return result;
153             if (0 != (result = name.compareTo(other.name))) return result;
154             return numericCode - other.numericCode;
155         }
156     }
157 
158     private static IsoCurrencyParser INSTANCE_WITHOUT_EXTENSIONS = new IsoCurrencyParser(false);
159     private static IsoCurrencyParser INSTANCE_WITH_EXTENSIONS = new IsoCurrencyParser(true);
160 
getInstance(boolean useCLDRExtensions)161     public static IsoCurrencyParser getInstance(boolean useCLDRExtensions) {
162         return useCLDRExtensions ? INSTANCE_WITH_EXTENSIONS : INSTANCE_WITHOUT_EXTENSIONS;
163     }
164 
getInstance()165     public static IsoCurrencyParser getInstance() {
166         return getInstance(true);
167     }
168 
getCodeList()169     public Relation<String, Data> getCodeList() {
170         return codeList;
171     }
172 
IsoCurrencyParser(boolean useCLDRExtensions)173     private IsoCurrencyParser(boolean useCLDRExtensions) {
174 
175         ISOCurrencyHandler isoCurrentHandler = new ISOCurrencyHandler();
176         XMLFileReader xfr = new XMLFileReader().setHandler(isoCurrentHandler);
177         xfr.readCLDRResource(ISO_CURRENT_CODES_XML, -1, false);
178         if (useCLDRExtensions) {
179             xfr.readCLDRResource(CLDR_EXTENSIONS_XML, -1, false);
180         }
181         if (exceptionList.size() != 0) {
182             throw new IllegalArgumentException(exceptionList.toString());
183         }
184         codeList.freeze();
185         countryToCodes.freeze();
186     }
187 
188     /*
189      * private Relation<String,Data> codeList = new Relation(new TreeMap(), TreeSet.class, null);
190      * private String version;
191      */
192 
getCountryToCodes()193     public Relation<String, String> getCountryToCodes() {
194         return countryToCodes;
195     }
196 
getCountryCode(String iso4217Country)197     public static String getCountryCode(String iso4217Country) {
198         iso4217Country = iso4217Country.trim();
199         if (iso4217Country.startsWith("\"")) {
200             iso4217Country = iso4217Country.substring(1, iso4217Country.length() - 1);
201         }
202         String name = iso4217CountryToCountryCode.get(iso4217Country);
203         if (name != null) return name;
204         if (iso4217Country.startsWith("ZZ")) {
205             return "ZZ";
206         }
207         exceptionList.add(String.format(CldrUtility.LINE_SEPARATOR + "\t\t.put(\"%s\", \"XXX\") // fix XXX and add to COUNTRY_CORRECTIONS in "
208             + StackTracker.currentElement(0).getFileName(), iso4217Country));
209         return "ZZ";
210     }
211 
212     public class ISOCurrencyHandler extends XMLFileReader.SimpleHandler {
213 
214         // This Set represents the entries in ISO4217 which we know to be bad. I have sent e-mail
215         // to the ISO 4217 Maintenance agency attempting to get them removed. Once that happens,
216         // we can remove these as well.
217         // SVC - El Salvador Colon - not used anymore ( uses USD instead )
218         // ZWL - Last Zimbabwe Dollar - abandoned due to hyper-inflation.
219         Set<String> KNOWN_BAD_ISO_DATA_CODES = new TreeSet<>(Arrays.asList("SVC", "ZWL"));
220         String country_code;
221         String currency_name;
222         String alphabetic_code;
223         int numeric_code;
224         int minor_unit;
225 
226         /**
227          * Finish processing anything left hanging in the file.
228          */
cleanup()229         public void cleanup() {
230         }
231 
232         @Override
handlePathValue(String path, String value)233         public void handlePathValue(String path, String value) {
234             try {
235                 XPathParts parts = XPathParts.getFrozenInstance(path);
236                 String type = parts.getElement(-1);
237                 if (type.equals("CtryNm")) {
238                     value = value.replaceAll("\n", "");
239                     country_code = getCountryCode(value);
240                     if (country_code == null) {
241                         country_code = "ZZ";
242                     }
243                     alphabetic_code = "XXX";
244                     numeric_code = -1;
245                     minor_unit = 0;
246                 } else if (type.equals("CcyNm")) {
247                     currency_name = value;
248                 } else if (type.equals("Ccy")) {
249                     alphabetic_code = value;
250                 } else if (type.equals("CcyNbr")) {
251                     try {
252                         numeric_code = Integer.valueOf(value);
253                     } catch (NumberFormatException ex) {
254                         numeric_code = -1;
255                     }
256                 } else if (type.equals("CcyMnrUnts")) {
257                     try {
258                         minor_unit = Integer.valueOf(value);
259                     } catch (NumberFormatException ex) {
260                         minor_unit = 2;
261                     }
262                 }
263 
264                 if (type.equals("CcyMnrUnts") && alphabetic_code.length() > 0
265                     && !KNOWN_BAD_ISO_DATA_CODES.contains(alphabetic_code)) {
266                     Data data = new Data(country_code, currency_name, numeric_code, minor_unit);
267                     codeList.put(alphabetic_code, data);
268                     countryToCodes.put(data.getCountryCode(), alphabetic_code);
269                 }
270 
271             } catch (Exception e) {
272                 throw (IllegalArgumentException) new IllegalArgumentException("path: "
273                     + path + ",\tvalue: " + value).initCause(e);
274             }
275         }
276     }
277 }
278