Merge pull request #5714 from alexischr/update_bockbuild
[mono.git] / tools / locale-builder / Driver.cs
1 //
2 // Driver.cs
3 //
4 // Authors:
5 //  Jackson Harper (jackson@ximian.com)
6 //  Atsushi Enomoto (atsushi@ximian.com)
7 //      Marek Safar  <marek.safar@gmail.com>
8 //
9 // (C) 2004-2005 Novell, Inc (http://www.novell.com)
10 // Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com)
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 //
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 //
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System;
33 using System.IO;
34 using System.Text;
35 using System.Xml;
36 using System.Globalization;
37 using System.Text.RegularExpressions;
38 using System.Collections.Generic;
39 using System.Linq;
40
41 namespace Mono.Tools.LocaleBuilder
42 {
43         public class Driver
44         {
45                 static readonly string data_root = Path.Combine ("CLDR", "common");
46
47                 public static void Main (string[] args)
48                 {
49                         Driver d = new Driver ();
50                         ParseArgs (args, d);
51                         d.Run ();
52                 }
53
54                 private static void ParseArgs (string[] args, Driver d)
55                 {
56                         for (int i = 0; i < args.Length; i++) {
57                                 if (args[i] == "--lang" && i + 1 < args.Length)
58                                         d.Lang = args[++i];
59                                 else if (args[i] == "--locales" && i + 1 < args.Length)
60                                         d.Locales = args[++i];
61                                 else if (args[i] == "--header" && i + 1 < args.Length)
62                                         d.HeaderFileName = args[++i];
63                                 else if (args[i] == "--compare")
64                                         d.OutputCompare = true;
65                         }
66                 }
67
68                 private string lang;
69                 private string locales;
70                 private string header_name;
71                 List<CultureInfoEntry> cultures;
72                 Dictionary<string, string> region_currency;
73                 Dictionary<string, string> currency_fractions;
74                 Dictionary<string, string> extra_parent_locales; 
75
76                 // The lang is the language that display names will be displayed in
77                 public string Lang
78                 {
79                         get
80                         {
81                                 if (lang == null)
82                                         lang = "en";
83                                 return lang;
84                         }
85                         set { lang = value; }
86                 }
87
88                 public string Locales
89                 {
90                         get { return locales; }
91                         set { locales = value; }
92                 }
93
94                 public string HeaderFileName
95                 {
96                         get
97                         {
98                                 if (header_name == null)
99                                         return "culture-info-tables.h";
100                                 return header_name;
101                         }
102                         set { header_name = value; }
103                 }
104
105                 public bool OutputCompare { get; set; }
106
107                 void Print ()
108                 {
109                         cultures.Sort ((a, b) => int.Parse (a.LCID.Substring (2), NumberStyles.HexNumber).CompareTo (int.Parse (b.LCID.Substring (2), NumberStyles.HexNumber)));
110
111                         var writer = Console.Out;
112
113                         foreach (var c in cultures) {
114                                 writer.WriteLine ("Name: {0}, LCID {1}", c.OriginalName, c.LCID);
115
116                                 writer.WriteLine ("{0}: {1}", "DisplayName", c.DisplayName);
117                                 writer.WriteLine ("{0}: {1}", "EnglishName", c.EnglishName);
118                                 writer.WriteLine ("{0}: {1}", "NativeName", c.NativeName);
119                                 // writer.WriteLine ("{0}: {1}", "OptionalCalendars", c.OptionalCalendars);
120                                 writer.WriteLine ("{0}: {1}", "ThreeLetterISOLanguageName", c.ThreeLetterISOLanguageName);
121                                 writer.WriteLine ("{0}: {1}", "ThreeLetterWindowsLanguageName", c.ThreeLetterWindowsLanguageName);
122                                 writer.WriteLine ("{0}: {1}", "TwoLetterISOLanguageName", c.TwoLetterISOLanguageName);
123                                 writer.WriteLine ("{0}: {1}", "Calendar", GetCalendarType (c.CalendarType));
124
125                                 var df = c.DateTimeFormatEntry;
126                                 writer.WriteLine ("-- DateTimeFormat --");
127                                 Dump (writer, df.AbbreviatedDayNames, "AbbreviatedDayNames");
128                                 Dump (writer, df.AbbreviatedMonthGenitiveNames, "AbbreviatedMonthGenitiveNames");
129                                 Dump (writer, df.AbbreviatedMonthNames, "AbbreviatedMonthNames");
130                                 writer.WriteLine ("{0}: {1}", "AMDesignator", df.AMDesignator);
131                                 writer.WriteLine ("{0}: {1}", "CalendarWeekRule", (CalendarWeekRule) df.CalendarWeekRule);
132                                 writer.WriteLine ("{0}: {1}", "DateSeparator", df.DateSeparator);
133                                 Dump (writer, df.DayNames, "DayNames");
134                                 writer.WriteLine ("{0}: {1}", "FirstDayOfWeek", (DayOfWeek) df.FirstDayOfWeek);
135 //                              Dump (writer, df.GetAllDateTimePatterns (), "GetAllDateTimePatterns");
136 //                              writer.WriteLine ("{0}: {1}", "LongDatePattern", df.LongDatePattern);
137 //                              writer.WriteLine ("{0}: {1}", "LongTimePattern", df.LongTimePattern);
138                                 writer.WriteLine ("{0}: {1}", "MonthDayPattern", df.MonthDayPattern);
139                                 Dump (writer, df.MonthGenitiveNames, "MonthGenitiveNames");
140                                 Dump (writer, df.MonthNames, "MonthNames");
141                                 writer.WriteLine ("{0}: {1}", "NativeCalendarName", df.NativeCalendarName);
142                                 writer.WriteLine ("{0}: {1}", "PMDesignator", df.PMDesignator);
143 //                              writer.WriteLine ("{0}: {1}", "ShortDatePattern", df.ShortDatePattern);
144                                 Dump (writer, df.ShortestDayNames, "ShortestDayNames");
145 //                              writer.WriteLine ("{0}: {1}", "ShortTimePattern", df.ShortTimePattern);
146                                 writer.WriteLine ("{0}: {1}", "TimeSeparator", df.TimeSeparator);
147 //                              writer.WriteLine ("{0}: {1}", "YearMonthPattern", df.YearMonthPattern);
148
149                                 var ti = c.TextInfoEntry;
150                                 writer.WriteLine ("-- TextInfo --");
151                                 writer.WriteLine ("{0}: {1}", "ANSICodePage", ti.ANSICodePage);
152                                 writer.WriteLine ("{0}: {1}", "EBCDICCodePage", ti.EBCDICCodePage);
153                                 writer.WriteLine ("{0}: {1}", "IsRightToLeft", ti.IsRightToLeft);
154                                 writer.WriteLine ("{0}: {1}", "ListSeparator", ti.ListSeparator);
155                                 writer.WriteLine ("{0}: {1}", "MacCodePage", ti.MacCodePage);
156                                 writer.WriteLine ("{0}: {1}", "OEMCodePage", ti.OEMCodePage);
157
158                                 var nf = c.NumberFormatEntry;
159                                 writer.WriteLine ("-- NumberFormat --");
160                                 writer.WriteLine ("{0}: {1}", "CurrencyDecimalDigits", nf.CurrencyDecimalDigits);
161                                 writer.WriteLine ("{0}: {1}", "CurrencyDecimalSeparator", nf.CurrencyDecimalSeparator);
162                                 writer.WriteLine ("{0}: {1}", "CurrencyGroupSeparator", nf.CurrencyGroupSeparator);
163                                 Dump (writer, nf.CurrencyGroupSizes, "CurrencyGroupSizes", true);
164                                 writer.WriteLine ("{0}: {1}", "CurrencyNegativePattern", nf.CurrencyNegativePattern);
165                                 writer.WriteLine ("{0}: {1}", "CurrencyPositivePattern", nf.CurrencyPositivePattern);
166                                 writer.WriteLine ("{0}: {1}", "CurrencySymbol", nf.CurrencySymbol);
167                                 writer.WriteLine ("{0}: {1}", "DigitSubstitution", nf.DigitSubstitution);
168                                 writer.WriteLine ("{0}: {1}", "NaNSymbol", nf.NaNSymbol);
169                                 Dump (writer, nf.NativeDigits, "NativeDigits");
170                                 writer.WriteLine ("{0}: {1}", "NegativeInfinitySymbol", nf.NegativeInfinitySymbol);
171                                 writer.WriteLine ("{0}: {1}", "NegativeSign", nf.NegativeSign);
172                                 writer.WriteLine ("{0}: {1}", "NumberDecimalDigits", nf.NumberDecimalDigits);
173                                 writer.WriteLine ("{0}: {1}", "NumberDecimalSeparator", nf.NumberDecimalSeparator);
174                                 writer.WriteLine ("{0}: {1}", "NumberGroupSeparator", nf.NumberGroupSeparator);
175                                 Dump (writer, nf.NumberGroupSizes, "NumberGroupSizes", true);
176                                 writer.WriteLine ("{0}: {1}", "NumberNegativePattern", nf.NumberNegativePattern);
177                                 writer.WriteLine ("{0}: {1}", "PercentNegativePattern", nf.PercentNegativePattern);
178                                 writer.WriteLine ("{0}: {1}", "PercentPositivePattern", nf.PercentPositivePattern);
179                                 writer.WriteLine ("{0}: {1}", "PercentSymbol", nf.PercentSymbol);
180                                 writer.WriteLine ("{0}: {1}", "PerMilleSymbol", nf.PerMilleSymbol);
181                                 writer.WriteLine ("{0}: {1}", "PositiveInfinitySymbol", nf.PositiveInfinitySymbol);
182                                 writer.WriteLine ("{0}: {1}", "PositiveSign", nf.PositiveSign);
183
184                                 if (c.RegionInfoEntry != null) {
185                                         var ri = c.RegionInfoEntry;
186                                         writer.WriteLine ("-- RegionInfo --");
187                                         writer.WriteLine ("{0}: {1}", "CurrencyEnglishName", ri.CurrencyEnglishName);
188                                         writer.WriteLine ("{0}: {1}", "CurrencyNativeName", ri.CurrencyNativeName);
189                                         writer.WriteLine ("{0}: {1}", "CurrencySymbol", ri.CurrencySymbol);
190                                         writer.WriteLine ("{0}: {1}", "DisplayName", ri.DisplayName);
191                                         writer.WriteLine ("{0}: {1}", "EnglishName", ri.EnglishName);
192                                         writer.WriteLine ("{0}: {1}", "GeoId", ri.GeoId);
193                                         writer.WriteLine ("{0}: {1}", "IsMetric", ri.IsMetric);
194                                         writer.WriteLine ("{0}: {1}", "ISOCurrencySymbol", ri.ISOCurrencySymbol);
195                                         writer.WriteLine ("{0}: {1}", "Name", ri.Name);
196                                         writer.WriteLine ("{0}: {1}", "NativeName", ri.NativeName);
197                                         writer.WriteLine ("{0}: {1}", "ThreeLetterISORegionName", ri.ThreeLetterISORegionName);
198                                         writer.WriteLine ("{0}: {1}", "ThreeLetterWindowsRegionName", ri.ThreeLetterWindowsRegionName);
199                                         writer.WriteLine ("{0}: {1}", "TwoLetterISORegionName", ri.TwoLetterISORegionName);
200                                 }
201
202                                 writer.WriteLine ();
203                         }
204                 }
205
206                 static Type GetCalendarType (CalendarType ct)
207                 {
208                         switch (ct) {
209                         case CalendarType.Gregorian:
210                                 return typeof (GregorianCalendar);
211                         case CalendarType.HijriCalendar:
212                                 return typeof (HijriCalendar);
213                         case CalendarType.ThaiBuddhist:
214                                 return typeof (ThaiBuddhistCalendar);
215                         case CalendarType.UmAlQuraCalendar:
216                                 return typeof (UmAlQuraCalendar);
217                         default:
218                                 throw new NotImplementedException ();
219                         }
220                 }
221
222                 static void Dump<T> (TextWriter tw, IList<T> values, string name, bool stopOnNull = false) where T : class
223                 {
224                         tw.Write (name);
225                         tw.Write (": ");
226
227                         for (int i = 0; i < values.Count; ++i) {
228                                 var v = values[i];
229
230                                 if (stopOnNull && v == null)
231                                         break;
232
233                                 if (i > 0)
234                                         tw.Write (", ");
235
236                                 tw.Write (v);
237                         }
238
239                         tw.WriteLine ();
240                 }
241
242                 void Run ()
243                 {
244                         Regex locales_regex = null;
245                         if (Locales != null)
246                                 locales_regex = new Regex (Locales);
247
248                         cultures = new List<CultureInfoEntry> ();
249                         var regions = new List<RegionInfoEntry> ();
250
251
252                         var supplemental = GetXmlDocument (Path.Combine (data_root, "supplemental", "supplementalData.xml"));
253
254                         // Read currencies info
255                         region_currency = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
256                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/currencyData/region")) {
257                                 var child = entry.SelectSingleNode ("currency");
258                                 region_currency.Add (entry.Attributes["iso3166"].Value, child.Attributes["iso4217"].Value);
259                         }
260
261                         // Parent locales
262                         extra_parent_locales = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
263                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/parentLocales/parentLocale")) {
264                                 var parent = entry.Attributes["parent"].Value;
265
266                                 if (parent == "root")
267                                         continue;
268
269                                 var locales = entry.Attributes["locales"].Value;
270                                 foreach (var locale in locales.Split (' '))
271                                         extra_parent_locales.Add (locale, parent);
272                         }
273
274                         // CLDR has habits of completely removing cultures data between release but we don't want to break
275                         // existing code
276                         var knownLCIDs = new HashSet<string> () {
277                                 "ar", "bg", "ca", "zh_Hans", "zh_CHS", "cs", "da", "de", "el", "en", "es", "fi", "fr", "he", "hu", "is", "it", "ja", "ko", "nl",
278                                 "no", "pl", "pt", "rm", "ro", "ru", "hr", "sk", "sq", "sv", "th", "tr", "ur", "id", "uk", "be", "sl", "et", "lv", "lt", "tg", "fa",
279                                 "vi", "hy", "az", "eu", "mk", "st", "ts", "tn", "xh", "zu", "af", "ka", "fo", "hi", "mt", "se", "ga", "ms", "kk", "ky", "sw", "uz",
280                                 "bn", "pa", "gu", "or", "ta", "te", "kn", "ml", "as", "mr", "mn", "bo", "cy", "km", "lo", "my", "gl", "kok", "si", "chr", "am", "tzm",
281                                 "ne", "ps", "fil", "ff", "ha", "yo", "nso", "kl", "ig", "om", "ti", "haw", "so", "ii", "br", "gsw", "sah", "rw", "gd", "ar_SA", "bg_BG",
282                                 "ca_ES", "zh_TW", "cs_CZ", "da_DK", "de_DE", "el_GR", "en_US", "fi_FI", "fr_FR", "he_IL", "hu_HU", "is_IS", "it_IT", "ja_JP", "ko_KR",
283                                 "nl_NL", "nb_NO", "pl_PL", "pt_BR", "rm_CH", "ro_RO", "ru_RU", "hr_HR", "sk_SK", "sq_AL", "sv_SE", "th_TH", "tr_TR", "ur_PK", "id_ID",
284                                 "uk_UA", "be_BY", "sl_SI", "et_EE", "lv_LV", "lt_LT", "tg_Cyrl_TJ", "fa_IR", "vi_VN", "hy_AM", "az_Latn_AZ", "eu_ES", "mk_MK", "st_ZA",
285                                 "ts_ZA", "tn_ZA", "xh_ZA", "zu_ZA", "af_ZA", "ka_GE", "fo_FO", "hi_IN", "mt_MT", "se_NO", "sw_KE", "uz_Latn_UZ", "bn_IN", "gu_IN",
286                                 "or_IN", "ta_IN", "te_IN", "kn_IN", "ml_IN", "as_IN", "mr_IN", "bo_CN", "cy_GB", "km_KH", "lo_LA", "my_MM", "gl_ES", "kok_IN", "si_LK",
287                                 "am_ET", "ne_NP", "ps_AF", "fil_PH", "ha_Latn_NG", "yo_NG", "nso_ZA", "kl_GL", "ig_NG", "om_ET", "ti_ET", "haw_US", "so_SO", "ii_CN",
288                                 "br_FR", "sah_RU", "rw_RW", "gd_GB", "ar_IQ", "zh_CN", "de_CH", "en_GB", "es_MX", "fr_BE", "it_CH", "nl_BE", "nn_NO", "pt_PT", "ro_MD",
289                                 "ru_MD", "sv_FI", "ur_IN", "az_Cyrl_AZ", "tn_BW", "ga_IE", "uz_Cyrl_UZ", "bn_BD", "pa_Arab_PK", "ta_LK", "ne_IN", "ti_ER", "ar_EG",
290                                 "zh_HK", "de_AT", "en_AU", "es_ES", "fr_CA", "se_FI", "ar_LY", "zh_SG", "de_LU", "en_CA", "es_GT", "fr_CH", "hr_BA", "ar_DZ", "zh_MO",
291                                 "de_LI", "en_NZ", "es_CR", "fr_LU", "bs_Latn_BA", "ar_MA", "en_IE", "es_PA", "fr_MC", "sr_Latn_BA", "ar_TN", "en_ZA", "es_DO", "sr_Cyrl_BA",
292                                 "ar_OM", "en_JM", "es_VE", "fr_RE", "bs_Cyrl_BA", "ar_YE", "es_CO", "fr_CD", "sr_Latn_RS", "ar_SY", "en_BZ", "es_PE", "fr_SN", "sr_Cyrl_RS",
293                                 "ar_JO", "en_TT", "es_AR", "fr_CM", "sr_Latn_ME", "ar_LB", "en_ZW", "es_EC", "fr_CI", "sr_Cyrl_ME", "ar_KW", "en_PH", "es_CL", "fr_ML",
294                                 "ar_AE", "es_UY", "fr_MA", "ar_BH", "en_HK", "es_PY", "fr_HT", "ar_QA", "en_IN", "es_BO", "es_SV", "en_SG", "es_HN", "es_NI", "es_PR",
295                                 "es_US", "es_CU", "bs_Cyrl", "bs_Latn", "sr_Cyrl", "sr_Latn", "az_Cyrl", "zh", "nn", "bs", "az_Latn", "uz_Cyrl", "mn_Cyrl", "zh_Hant",
296                                 "zh_CHT", "nb", "sr", "tg_Cyrl", "uz_Latn", "pa_Arab", "tzm_Latn", "ha_Latn",
297                                 "hsb", "tk", "fy", "lb", "ug", "hsb_DE", "ms_MY", "kk_KZ", "ky_KG", "tk_TM", "mn_MN", "fy_NL", "lb_LU", "ug_CN", "gsw_FR", "ca_ES_valencia",
298                                 "dsb_DE", "se_SE", "ms_BN", "smn_FI", "en_MY", "smn", "dsb"
299                         };
300
301                         var lcdids = GetXmlDocument ("lcids.xml");
302                         foreach (XmlNode lcid in lcdids.SelectNodes ("lcids/lcid")) {
303                                 var name = lcid.Attributes["name"].Value;
304
305                                 if (locales_regex != null && !locales_regex.IsMatch (name))
306                                         continue;
307
308                                 var ci = new CultureInfoEntry ();
309                                 ci.LCID = lcid.Attributes["id"].Value;
310                                 ci.ParentLcid = lcid.Attributes["parent"].Value;
311                                 ci.TwoLetterISOLanguageName = lcid.Attributes["iso2"].Value;
312                                 ci.ThreeLetterISOLanguageName = lcid.Attributes["iso3"].Value;
313                                 ci.ThreeLetterWindowsLanguageName = lcid.Attributes["win"].Value;
314                                 ci.OriginalName = name.Replace ('_', '-');
315                                 ci.TextInfoEntry = new TextInfoEntry ();
316                                 ci.NumberFormatEntry = new NumberFormatEntry ();
317
318                                 if (!Import (ci, name)) {
319                                         if (knownLCIDs.Contains (name)) {
320                                                 Console.WriteLine ($"Missing previously available culture `{ name }' data");
321                                                 return;
322                                         }
323
324                                         continue;
325                                 }
326
327
328                                 if (!knownLCIDs.Contains (name)) {
329                                         Console.WriteLine ($"New culture `{ name }' data available");
330                                         return;
331                                 }
332
333                                 cultures.Add (ci);
334                         }
335
336                         var doc_english = GetXmlDocument (Path.Combine (data_root, "main", "en.xml"));
337
338                         //
339                         // Fill all EnglishName values from en.xml language file
340                         //
341                         foreach (var ci in cultures) {
342                                 var el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", ci.Language));
343                                 if (el != null)
344                                         ci.EnglishName = el.InnerText;
345
346                                 string s = null;
347                                 if (ci.Script != null) {
348                                         el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/scripts/script[@type='{0}']", ci.Script));
349                                         if (el != null)
350                                                 s = el.InnerText;
351                                 }
352
353                                 if (ci.Territory != null) {
354                                         el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci.Territory));
355                                         if (el != null) {
356                                                 if (s == null)
357                                                         s = el.InnerText;
358                                                 else
359                                                         s = string.Join (", ", s, el.InnerText);
360                                         }
361                                 }
362
363                                 switch (ci.ThreeLetterWindowsLanguageName) {
364                                 case "CHT":
365                                         s = "Traditional";
366                                         break;
367                                 case "CHS":
368                                         s = "Simplified";
369                                         break;
370                                 }
371
372                                 if (s != null)
373                                         ci.EnglishName = string.Format ("{0} ({1})", ci.EnglishName, s);
374
375                                 // Special case legacy chinese
376                                 if (ci.OriginalName == "zh-CHS" || ci.OriginalName == "zh-CHT")
377                                         ci.EnglishName += " Legacy";
378
379                                 // Mono is not localized and supports english only, hence the name will always be same
380                                 ci.DisplayName = ci.EnglishName;
381                         }
382
383                         //
384                         // Fill culture hierarchy for easier data manipulation
385                         //
386                         foreach (var ci in cultures) {
387                                 foreach (var p in cultures.Where (l => ci.LCID == l.ParentLcid)) {
388                                         ci.Children.Add (p);
389                                 }
390                         }
391
392                         currency_fractions = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
393                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/currencyData/fractions/info")) {
394                                 currency_fractions.Add (entry.Attributes["iso4217"].Value, entry.Attributes["digits"].Value);
395                         }
396
397                         var territory2dayofweek = new Dictionary<string, DayOfWeek> (StringComparer.OrdinalIgnoreCase);
398                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/firstDay")) {
399
400                                 if (entry.Attributes ["alt"] != null)
401                                         continue;
402
403                                 DayOfWeek dow;
404                                 switch (entry.Attributes["day"].Value) {
405                                 case "mon":
406                                         dow = DayOfWeek.Monday;
407                                         break;
408                                 case "fri":
409                                         dow = DayOfWeek.Friday;
410                                         break;
411                                 case "sat":
412                                         dow = DayOfWeek.Saturday;
413                                         break;
414                                 case "sun":
415                                         dow = DayOfWeek.Sunday;
416                                         break;
417                                 default:
418                                         throw new NotImplementedException ();
419                                 }
420
421                                 var territories = entry.Attributes["territories"].Value.Split (new [] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
422                                 foreach (var t in territories) {
423                                         var tr = t.Trim ();
424                                         if (tr.Length == 0)
425                                                 continue;
426
427                                         territory2dayofweek.Add (tr, dow);
428                                 }
429                         }
430
431                         var territory2wr = new Dictionary<string, CalendarWeekRule> (StringComparer.OrdinalIgnoreCase);
432                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/minDays")) {
433                                 CalendarWeekRule rule;
434
435                                 switch (entry.Attributes["count"].InnerText) {
436                                 case "1":
437                                         rule = CalendarWeekRule.FirstDay;
438                                         break;
439                                 case "4":
440                                         rule = CalendarWeekRule.FirstFourDayWeek;
441                                         break;
442                                 default:
443                                         throw new NotImplementedException ();
444                                 }
445
446                                 var territories = entry.Attributes["territories"].InnerText.Split ();
447                                 foreach (var t in territories)
448                                         territory2wr[t] = rule;
449                         }
450
451                         //
452                         // Fill all territory speficic data where territory is available
453                         //
454                         var non_metric = new HashSet<string> ();
455                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/measurementData/measurementSystem[@type='US']")) {
456                                 var territories = entry.Attributes["territories"].InnerText.Split ();
457                                 foreach (var t in territories)
458                                         non_metric.Add (t);
459                         }
460
461                         foreach (var ci in cultures) {
462                                 if (ci.Territory == null)
463                                         continue;
464
465                                 DayOfWeek value;
466                                 if (territory2dayofweek.TryGetValue (ci.Territory, out value)) {
467                                         ci.DateTimeFormatEntry.FirstDayOfWeek = (int) value;
468                                 }
469
470                                 CalendarWeekRule rule;
471                                 if (territory2wr.TryGetValue (ci.Territory, out rule)) {
472                                         ci.DateTimeFormatEntry.CalendarWeekRule = (int) rule;
473                                 }
474
475                                 RegionInfoEntry region = regions.Where (l => l.Name == ci.Territory).FirstOrDefault ();
476                                 if (region == null) {
477                                         region = new RegionInfoEntry () {
478                                                 CurrencySymbol = ci.NumberFormatEntry.CurrencySymbol,
479                                                 EnglishName = ci.EnglishName,
480                                                 NativeName = ci.NativeTerritoryName,
481                                                 Name = ci.Territory,
482                                                 TwoLetterISORegionName = ci.Territory,
483                                                 CurrencyNativeName = ci.NativeCurrencyName
484                                         };
485
486                                         var tc = supplemental.SelectSingleNode (string.Format ("supplementalData/codeMappings/territoryCodes[@type='{0}']", ci.Territory));
487                                         region.ThreeLetterISORegionName = tc?.Attributes["alpha3"]?.Value ?? "---";
488                                         region.ThreeLetterWindowsRegionName = region.ThreeLetterISORegionName;
489
490                                         var el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci.Territory));
491                                         region.EnglishName = el.InnerText;
492                                         region.DisplayName = region.EnglishName;
493
494                                         string curr;
495                                         if (!region_currency.TryGetValue (ci.Territory, out curr))
496                                                 curr = "---";
497                                         region.ISOCurrencySymbol = curr;
498
499                                         el = doc_english.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", region.ISOCurrencySymbol));
500                                         region.CurrencyEnglishName = el?.InnerText ?? "---";
501
502                                         if (non_metric.Contains (ci.Territory))
503                                                 region.IsMetric = false;
504
505                                         var lcdid_value = int.Parse (ci.LCID.Substring (2), NumberStyles.HexNumber);
506                                         Patterns.FillValues (lcdid_value, region);
507                                         regions.Add (region);
508                                 }
509
510                                 string fraction_value;
511                                 if (currency_fractions.TryGetValue (region.ISOCurrencySymbol, out fraction_value)) {
512                                         ci.NumberFormatEntry.CurrencyDecimalDigits = fraction_value;
513                                 }
514
515                                 ci.RegionInfoEntry = region;
516                         }
517
518                         //
519                         // Fill neutral cultures territory data
520                         //
521                         foreach (var ci in cultures) {
522                                 var dtf = ci.DateTimeFormatEntry;
523                                 if (dtf.FirstDayOfWeek == null) {
524                                         switch (ci.Name) {
525                                         case "ar":
526                                                 dtf.FirstDayOfWeek = (int) DayOfWeek.Saturday;
527                                                 break;
528                                         case "en":
529                                         case "pt":
530                                         case "zh-Hans":
531                                                 dtf.FirstDayOfWeek = (int) DayOfWeek.Sunday;
532                                                 break;
533                                         case "es":
534                                         case "fr":
535                                         case "bn":
536                                         case "sr-Cyrl":
537                                         case "sr-Latn":
538                                         case "ta":
539                                                 dtf.FirstDayOfWeek = (int) DayOfWeek.Monday;
540                                                 break;
541                                         default:
542                                                 List<int?> all_fdow = new List<int?> ();
543                                                 GetAllChildrenValues (ci, all_fdow, l => l.DateTimeFormatEntry.FirstDayOfWeek);
544                                                 var children = all_fdow.Where (l => l != null).Distinct ().ToList ();
545
546                                                 if (children.Count == 1) {
547                                                         dtf.FirstDayOfWeek = children[0];
548                                                 } else if (children.Count == 0) {
549                                                         if (!ci.HasMissingLocale)
550                                                                 Console.WriteLine ("No week data for `{0}'", ci.Name);
551
552                                                         // Default to Sunday
553                                                         dtf.FirstDayOfWeek = (int) DayOfWeek.Sunday;
554                                                 } else {
555                                                         // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
556                                                         // We have to manually disambiguate the correct entry (which is artofficial anyway)
557                                                         throw new ApplicationException (string.Format ("Ambiguous week data for `{0}'", ci.Name));
558                                                 }
559
560                                                 break;
561                                         }
562                                 }
563
564                                 if (dtf.CalendarWeekRule == null) {
565                                         switch (ci.Name) {
566                                         case "ar":
567                                         case "en":
568                                         case "es":
569                                         case "zh-Hans":
570                                         case "pt":
571                                         case "fr":
572                                         case "bn":
573                                                 dtf.CalendarWeekRule = (int) CalendarWeekRule.FirstDay;
574                                                 break;
575                                         default:
576                                                 List<int?> all_cwr = new List<int?> ();
577                                                 GetAllChildrenValues (ci, all_cwr, l => l.DateTimeFormatEntry.CalendarWeekRule);
578                                                 var children = all_cwr.Where (l => l != null).Distinct ().ToList ();
579
580                                                 if (children.Count == 1) {
581                                                         dtf.CalendarWeekRule = children[0];
582                                                 } else if (children.Count == 0) {
583                                                         if (!ci.HasMissingLocale)
584                                                                 Console.WriteLine ("No calendar week data for `{0}'", ci.Name);
585
586
587                                                         // Default to FirstDay
588                                                         dtf.CalendarWeekRule = (int) CalendarWeekRule.FirstDay;
589                                                 } else {
590                                                         // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
591                                                         // We have to manually disambiguate the correct entry (which is artofficial anyway)
592                                                         throw new ApplicationException (string.Format ("Ambiguous calendar data for `{0}'", ci.Name));
593                                                 }
594
595                                                 break;
596                                         }
597                                 }
598
599                                 var nfe = ci.NumberFormatEntry;
600                                 if (nfe.CurrencySymbol == null) {
601                                         switch (ci.Name) {
602                                         case "ar":
603                                                 nfe.CurrencySymbol = "ر.س.‏";
604                                                 break;
605                                         case "en":
606                                                 nfe.CurrencySymbol = "$";
607                                                 break;
608                                         case "bs":
609                                                 nfe.CurrencySymbol = "KM";
610                                                 break;
611                                         case "es":
612                                         case "fr":
613                                         case "de":
614                                         case "it":
615                                         case "se":
616                                                 nfe.CurrencySymbol = "€";
617                                                 break;
618                                         case "hr":
619                                                 nfe.CurrencySymbol = "kn";
620                                                 break;                          
621                                         case "pt":
622                                                 nfe.CurrencySymbol = "R$";
623                                                 break;
624                                         case "sv":
625                                                 nfe.CurrencySymbol = "kr";
626                                                 break;
627                                         case "ms":
628                                                 nfe.CurrencySymbol = "RM";
629                                                 break;
630                                         case "bn":
631                                                 nfe.CurrencySymbol = "টা";
632                                                 break;
633                                         case "sr-Cyrl":
634                                                 nfe.CurrencySymbol = "Дин.";
635                                                 break;
636                                         case "sr-Latn":
637                                         case "sr":
638                                                 nfe.CurrencySymbol = "Din.";
639                                                 break;
640                                         case "zh":
641                                         case "zh-Hans":
642                                                 nfe.CurrencySymbol = "¥";
643                                                 break;
644                                         case "zh-Hant":
645                                                 nfe.CurrencySymbol = "HK$";
646                                                 break;
647                                         case "ru":
648                                                 nfe.CurrencySymbol = "₽";
649                                                 break;
650                                         case "ur":
651                                                 nfe.CurrencySymbol = "Rs";
652                                                 break;
653                                         case "tn":
654                                                 nfe.CurrencySymbol = "R";
655                                                 break;
656                                         case "ta":
657                                                 nfe.CurrencySymbol = "₹";
658                                                 break;
659                                         case "ne":
660                                                 nfe.CurrencySymbol = "रु";
661                                                 break;
662                                         case "ti":
663                                                 nfe.CurrencySymbol = "Nfk";
664                                                 break;
665                                         case "ro":
666                                                 nfe.CurrencySymbol = "RON";
667                                                 break;
668                                         default:
669                                                 var all_currencies = new List<string> ();
670                                                 GetAllChildrenValues (ci, all_currencies, l => l.NumberFormatEntry.CurrencySymbol);
671                                                 var children = all_currencies.Where (l => l != null).Distinct ().ToList ();
672
673                                                 if (children.Count == 1) {
674                                                         nfe.CurrencySymbol = children[0];
675                                                 } else if (children.Count == 0) {
676                                                         if (!ci.HasMissingLocale)
677                                                                 Console.WriteLine ("No currency data for `{0}'", ci.Name);
678
679
680                                                 } else {
681                                                         // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
682                                                         // We have to manually disambiguate the correct entry (which is artofficial anyway)
683                                                         throw new ApplicationException (string.Format ("Ambiguous currency data for `{0}'. Possible values '{1}'", ci.Name, string.Join (", ", children)));
684                                                 }
685
686                                                 break;
687                                         }
688                                 }
689                         }
690
691                         if (OutputCompare)
692                                 Print ();
693
694                         regions.Sort (new RegionComparer ());
695                         for (int i = 0; i < regions.Count; ++i)
696                                 regions[i].Index = i;
697
698                         /**
699                          * Dump each table individually. Using StringBuilders
700                          * because it is easier to debug, should switch to just
701                          * writing to streams eventually.
702                          */
703                         using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
704                                 writer.NewLine = "\n";
705                                 writer.WriteLine ();
706                                 writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
707                                 writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
708                                 writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
709                                 writer.WriteLine ("\n");
710
711                                 writer.WriteLine ("#define NUM_CULTURE_ENTRIES {0}", cultures.Count);
712                                 writer.WriteLine ("#define NUM_REGION_ENTRIES {0}", regions.Count);
713
714                                 writer.WriteLine ("\n");
715
716                                 // Sort the cultures by lcid
717                                 cultures.Sort (new LcidComparer ());
718
719                                 StringBuilder builder = new StringBuilder ();
720                                 int row = 0;
721                                 int count = cultures.Count;
722                                 for (int i = 0; i < count; i++) {
723                                         CultureInfoEntry ci = cultures[i];
724                                         if (ci.DateTimeFormatEntry == null)
725                                                 continue;
726                                         ci.DateTimeFormatEntry.AppendTableRow (builder);
727                                         ci.DateTimeFormatEntry.Row = row++;
728                                         if (i + 1 < count)
729                                                 builder.Append (',');
730                                         builder.Append ('\n');
731                                 }
732
733                                 writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
734                                 writer.Write (builder);
735                                 writer.WriteLine ("};\n\n");
736
737                                 builder = new StringBuilder ();
738                                 row = 0;
739                                 for (int i = 0; i < count; i++) {
740                                         CultureInfoEntry ci = cultures[i];
741                                         if (ci.NumberFormatEntry == null)
742                                                 continue;
743                                         ci.NumberFormatEntry.AppendTableRow (builder);
744                                         ci.NumberFormatEntry.Row = row++;
745                                         if (i + 1 < count)
746                                                 builder.Append (',');
747                                         builder.Append ('\n');
748                                 }
749
750                                 writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
751                                 writer.Write (builder);
752                                 writer.WriteLine ("};\n\n");
753
754                                 builder = new StringBuilder ();
755                                 row = 0;
756                                 for (int i = 0; i < count; i++) {
757                                         CultureInfoEntry ci = cultures[i];
758                                         ci.AppendTableRow (builder);
759                                         ci.Row = row++;
760                                         if (i + 1 < count)
761                                                 builder.Append (',');
762                                         builder.Append ('\n');
763                                 }
764
765                                 writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
766                                 writer.Write (builder);
767                                 writer.WriteLine ("};\n\n");
768
769                                 cultures.Sort (new ExportNameComparer ()); // Sort based on name
770                                 builder = new StringBuilder ();
771                                 for (int i = 0; i < count; i++) {
772                                         CultureInfoEntry ci = cultures[i];
773                                         var name = ci.GetExportName ().ToLowerInvariant ();
774                                         builder.Append ("\t{" + Entry.EncodeStringIdx (name) + ", ");
775                                         builder.Append (ci.Row + "}");
776                                         if (i + 1 < count)
777                                                 builder.Append (',');
778
779                                         builder.AppendFormat ("\t /* {0} */", name);
780                                         builder.Append ('\n');
781                                 }
782
783                                 writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
784                                 writer.Write (builder);
785                                 writer.WriteLine ("};\n\n");
786
787                                 builder = new StringBuilder ();
788                                 int rcount = 0;
789                                 foreach (RegionInfoEntry r in regions) {
790                                         r.AppendTableRow (builder);
791                                         if (++rcount != regions.Count)
792                                                 builder.Append (',');
793
794                                         builder.Append ('\n');
795                                 }
796                                 writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
797                                 writer.Write (builder);
798                                 writer.WriteLine ("};\n\n");
799
800                                 builder = new StringBuilder ();
801                                 rcount = 0;
802                                 foreach (RegionInfoEntry ri in regions) {
803                                         builder.Append ("\t{" + Entry.EncodeStringIdx (ri.TwoLetterISORegionName) + ", ");
804                                         builder.Append (ri.Index + "}");
805                                         if (++rcount != regions.Count)
806                                                 builder.Append (',');
807                                         
808                                         builder.AppendFormat ("\t /* {0} */", ri.TwoLetterISORegionName);
809                                         builder.Append ('\n');
810                                 }
811
812                                 writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
813                                 writer.Write (builder);
814                                 writer.WriteLine ("};\n\n");
815
816                                 writer.WriteLine ("static const char locale_strings [] = {");
817                                 writer.Write (Entry.General.GetStrings ());
818                                 writer.WriteLine ("};\n\n");
819
820                                 writer.WriteLine ("static const char patterns [] = {");
821                                 writer.Write (Entry.Patterns.GetStrings ());
822                                 writer.WriteLine ("};\n\n");
823
824                                 writer.WriteLine ("static const char datetime_strings [] = {");
825                                 writer.Write (Entry.DateTimeStrings.GetStrings ());
826                                 writer.WriteLine ("};\n\n");
827
828                                 writer.WriteLine ("#endif\n");
829                         }
830                 }
831
832                 static void GetAllChildrenValues<T> (CultureInfoEntry entry, List<T> values, Func<CultureInfoEntry, T> selector)
833                 {
834                         foreach (var e in entry.Children) {
835                                 if (e == entry)
836                                         continue;
837
838                                 values.Add (selector (e));
839
840                                 foreach (var e2 in e.Children) {
841                                         GetAllChildrenValues (e2, values, selector);
842                                 }
843                         }
844                 }
845
846                 static XmlDocument GetXmlDocument (string path)
847                 {
848                         var doc = new XmlDocument ();
849                         doc.Load (new XmlTextReader (path) { /*DtdProcessing = DtdProcessing.Ignore*/ } );
850                         return doc;
851                 }
852
853                 bool Import (CultureInfoEntry data, string locale)
854                 {
855                         string fname = null;
856                         var sep = locale.Split ('_');
857                         data.Language = sep[0];
858
859                         // CLDR strictly follow ISO names, .NET does not
860                         // Replace names where non-iso2 is used, e.g. Norway
861                         if (data.Language != data.TwoLetterISOLanguageName) {
862                                 locale = data.TwoLetterISOLanguageName;
863                                 if (sep.Length > 1) {
864                                         locale += string.Join ("_", sep.Skip (1));
865                                 }
866                         }
867
868                         // Convert broken Chinese names to correct one
869                         switch (locale) {
870                         case "zh_CHS":
871                                 locale = "zh_Hans";
872                                 break;
873                         case "zh_CHT":
874                                 locale = "zh_Hant";
875                                 break;
876                         case "zh_CN":
877                                 locale = "zh_Hans_CN";
878                                 break;
879                         case "zh_HK":
880                                 locale = "zh_Hant_HK";
881                                 break;
882                         case "zh_SG":
883                                 locale = "zh_Hans_SG";
884                                 break;
885                         case "zh_TW":
886                                 locale = "zh_Hant_TW";
887                                 break;
888                         case "zh_MO":
889                                 locale = "zh_Hant_MO";
890                                 break;
891                         }
892
893                         sep = locale.Split ('_');
894
895                         string full_name = Path.Combine (data_root, "main", locale + ".xml");
896                         if (!File.Exists (full_name)) {
897                                 Console.WriteLine ("Missing locale file for `{0}'", locale);
898
899                                 // We could fill default values but that's not as simple as it seems. For instance for non-neutral
900                                 // cultures the next part could be territory or not.
901                                 return false;
902                         } else {
903                                 XmlDocument doc = null;
904
905                                 /*
906                                  * Locale generation is done in several steps, first we
907                                  * read the root file which is the base invariant data
908                                  * then the supplemental root data, 
909                                  * then the language file, the supplemental languages
910                                  * file then the locale file, then the supplemental
911                                  * locale file. Values in each descending file can
912                                  * overwrite previous values.
913                                  */
914                                 foreach (var part in sep) {
915                                         if (fname != null)
916                                                 fname += "_";
917
918                                         fname += part;
919
920                                         XmlDocument xml;
921                                         string extra;
922                                         if (extra_parent_locales.TryGetValue (fname, out extra)) {
923                                                 xml = GetXmlDocument (Path.Combine (data_root, "main", extra + ".xml"));
924                                                 if (doc == null)
925                                                         doc = xml;
926
927                                                 Import (xml, data);
928                                         }
929
930                                         xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
931                                         if (doc == null)
932                                                 doc = xml;
933
934                                         Import (xml, data);
935                                 }
936
937                                 //
938                                 // Extract localized locale name from language xml file. Have to do it after both language and territory are read
939                                 //
940                                 var el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", data.Language));
941                                 if (el != null)
942                                         data.NativeName = el.InnerText;
943
944                                 if (data.Territory != null) {
945                                         el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", data.Territory));
946                                         if (el != null) {
947                                                 // TODO: Should read <localePattern>
948                                                 data.NativeName = string.Format ("{0} ({1})", data.NativeName, el.InnerText);
949                                                 data.NativeTerritoryName = el.InnerText;
950                                         }
951
952                                         string currency;
953                                         // We have territory now we have to run the process again to extract currency symbol
954                                         if (region_currency.TryGetValue (data.Territory, out currency)) {
955                                                 fname = null;
956
957                                                 var xml = GetXmlDocument (Path.Combine (data_root, "main", "root.xml"));
958                                                 el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
959                                                 if (el != null)
960                                                         data.NumberFormatEntry.CurrencySymbol = el.InnerText;
961
962                                                 foreach (var part in sep) {
963                                                         if (fname != null)
964                                                                 fname += "_";
965
966                                                         fname += part;
967
968                                                         xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
969                                                         el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
970                                                         if (el != null)
971                                                                 data.NumberFormatEntry.CurrencySymbol = el.InnerText;
972
973                                                         el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", currency));
974                                                         if (el != null)
975                                                                 data.NativeCurrencyName = el.InnerText;
976                                                 }
977                                         }
978                                 }
979                         }
980
981                         // TODO: Don't have input data available but most values are 2 with few exceptions for 1 and 3
982                         // We don't add 3 as it's for some arabic states only
983                         switch (data.ThreeLetterISOLanguageName) {
984                         case "amh":
985                                 data.NumberFormatEntry.NumberDecimalDigits = 1;
986                                 break;
987                         default:
988                                 data.NumberFormatEntry.NumberDecimalDigits = 2;
989                                 break;
990                         }
991
992                         // TODO: For now we capture only native name for default calendar
993                         data.NativeCalendarNames[((int) data.CalendarType & 0xFF) - 1] = data.DateTimeFormatEntry.NativeCalendarName;
994
995                         var lcdid_value = int.Parse (data.LCID.Substring (2), NumberStyles.HexNumber);
996                         Patterns.FillValues (lcdid_value, data);
997
998                         return true;
999                 }
1000
1001                 void Import (XmlDocument doc, CultureInfoEntry ci)
1002                 {
1003                         XmlNodeList nodes;
1004                         XmlNode el;
1005
1006                         //
1007                         // Extract script & teritory
1008                         //
1009                         el = doc.SelectSingleNode ("ldml/identity/script");
1010                         if (el != null)
1011                                 ci.Script = el.Attributes["type"].Value;
1012
1013                         el = doc.SelectSingleNode ("ldml/identity/territory");
1014                         if (el != null)
1015                                 ci.Territory = el.Attributes["type"].Value;
1016
1017                         var df = ci.DateTimeFormatEntry;
1018
1019                         string calendar;
1020                         // Default calendar is for now always "gregorian"
1021                         switch (ci.OriginalName) {
1022                         case "th": case "th-TH":
1023                                 calendar = "buddhist";
1024                                 ci.CalendarType = CalendarType.ThaiBuddhist; // typeof (ThaiBuddhistCalendar);
1025                                 break;
1026                         case "ar": case "ar-SA":
1027                                 calendar = "islamic";
1028                                 ci.CalendarType = CalendarType.UmAlQuraCalendar; // typeof (UmAlQuraCalendar);
1029                                 break;
1030                         case "ps": case "ps-AF": case "prs": case "prs-AF": case "dv": case "dv-MV":
1031                                 calendar = "persian";
1032                                 ci.CalendarType = CalendarType.HijriCalendar; // typeof (HijriCalendar);
1033                                 break;
1034                         default:
1035                                 calendar = "gregorian";
1036                                 ci.CalendarType = CalendarType.Gregorian; // typeof (GregorianCalendar);
1037                                 ci.GregorianCalendarType = GregorianCalendarTypes.Localized;
1038                                 break;
1039                         }
1040
1041                         var node = doc.SelectSingleNode (string.Format ("ldml/dates/calendars/calendar[@type='{0}']", calendar));
1042                         if (node != null) {
1043                                 el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/types/type[@type='{0}']", calendar));
1044                                 if (el != null)
1045                                         df.NativeCalendarName = el.InnerText;
1046
1047
1048                                 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='wide']"/>
1049                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
1050                                 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
1051                                 nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='wide']/month");
1052                                 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
1053
1054                                 if (df.MonthNames != null) {
1055                                         if (ci.Name == "sv" || ci.Name == "sv-SE") {
1056                                                 ToLower (df.MonthNames);
1057                                         }
1058                                 }
1059
1060                                 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='abbreviated']"/>
1061                                 if (ci.Name == "ja" || ci.Name == "ja-JP") {
1062                                         // Use common number style
1063                                 } else {
1064                                         nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='abbreviated']/month");
1065                                         ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
1066                                         nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='abbreviated']/month");
1067                                         ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
1068                                 }
1069
1070                                 if (df.AbbreviatedMonthNames != null) {
1071                                         if (ci.Name == "sv" || ci.Name == "sv-SE") {
1072                                                 ToLower (df.AbbreviatedMonthNames);
1073                                         }
1074                                 }
1075
1076                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
1077                                 if (nodes != null) {
1078                                         ProcessAllNodes (nodes, df.MonthGenitiveNames, AddOrReplaceValue);
1079                                 }
1080
1081                                 // All values seem to match
1082                                 Array.Copy (df.AbbreviatedMonthNames, df.AbbreviatedMonthGenitiveNames, df.AbbreviatedMonthNames.Length);
1083
1084                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='wide']/day");
1085                                 ProcessAllNodes (nodes, df.DayNames, AddOrReplaceDayValue);
1086
1087                                 // Apply global rule first <alias source="locale" path="../../dayContext[@type='format']/dayWidth[@type='abbreviated']"/>
1088                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='abbreviated']/day");
1089                                 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
1090                                 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='abbreviated']/day");
1091                                 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
1092
1093                                 if (df.AbbreviatedDayNames != null) {
1094                                         if (ci.Name == "sv" || ci.Name == "sv-SE") {
1095                                                 ToLower (df.AbbreviatedDayNames);
1096                                         }
1097                                 }
1098
1099                                 // TODO: This is not really ShortestDayNames as .NET uses it
1100                                 // Apply global rules first <alias source="locale" path="../../dayContext[@type='stand-alone']/dayWidth[@type='narrow']"/>
1101                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='narrow']/day");
1102                                 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1103                                 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='narrow']/day");
1104                                 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1105 /*
1106                                 Cannot really be used it's too different to .NET and most app rely on it
1107  
1108                                 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='full']/dateFormat/pattern");
1109                                 if (el != null)
1110                                         df.LongDatePattern = ConvertDatePatternFormat (el.InnerText);
1111
1112                                 // Medium is our short
1113                                 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='medium']/dateFormat/pattern");
1114                                 if (el != null)
1115                                         df.ShortDatePattern = ConvertDatePatternFormat (el.InnerText);
1116
1117                                 // Medium is our Long
1118                                 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='medium']/timeFormat/pattern");
1119                                 if (el != null)
1120                                         df.LongTimePattern = ConvertTimePatternFormat (el.InnerText);
1121
1122                                 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='short']/timeFormat/pattern");
1123                                 if (el != null)
1124                                         df.ShortTimePattern = ConvertTimePatternFormat (el.InnerText);
1125
1126                                 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='yyyyMMMM']");
1127                                 if (el != null)
1128                                         df.YearMonthPattern = ConvertDatePatternFormat (el.InnerText);
1129
1130                                 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='MMMMdd']");
1131                                 if (el != null)
1132                                         df.MonthDayPattern = ConvertDatePatternFormat (el.InnerText);
1133 */
1134                                 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='am']");
1135                                 if (el == null)
1136                                         // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1137                                         el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='am']");
1138
1139                                 // Manual edits for exact .net compatiblity
1140                                 switch (ci.Name) {
1141                                 case "en-AU":
1142                                         df.AMDesignator = "AM";
1143                                         break;
1144                                 case "en-NZ":
1145                                         df.AMDesignator = "a.m.";
1146                                         break;
1147                                 default:
1148                                         if (el != null)
1149                                                 df.AMDesignator = el.InnerText;
1150                                         break;
1151                                 }
1152
1153                                 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='pm']");
1154                                 if (el == null)
1155                                         // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1156                                         el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='pm']");
1157
1158                                 switch (ci.Name) {
1159                                 case "en-AU":
1160                                         df.PMDesignator = "PM";
1161                                         break;
1162                                 case "en-NZ":
1163                                         df.PMDesignator = "p.m.";
1164                                         break;
1165                                 default:
1166                                         if (el != null)
1167                                                 df.PMDesignator = el.InnerText;
1168                                         break;
1169                                 }
1170                         }
1171
1172                         var ni = ci.NumberFormatEntry;
1173
1174                         node = doc.SelectSingleNode ("ldml/numbers/symbols");
1175                         if (node != null) {
1176                                 el = node.SelectSingleNode ("plusSign");
1177                                 if (el != null)
1178                                         ni.PositiveSign = el.InnerText;
1179
1180                                 // CLDR uses unicode negative sign for some culture (e.g sv, is, lt, don't kwnow why) but .NET always
1181                                 // uses simple "-" sign and what is worse the parsing code cannot deal with non-ASCII values
1182                                 ni.NegativeSign = "-";
1183
1184                                 /*
1185                                 el = node.SelectSingleNode ("minusSign");
1186                                 if (el != null) {
1187                                         switch (el.InnerText) {
1188                                         case "\u2212":
1189                                         case "\u200F\u002D": // Remove any right-to-left mark characters
1190                                         case "\u200E\u002D":
1191                                         case "\u061C\u2212":
1192                                         case "\u200F\u2212":
1193                                                 ni.NegativeSign = "-";
1194                                                 break;
1195                                         default:
1196                                                 ni.NegativeSign = el.InnerText;
1197                                                 break;
1198                                         }
1199                                 }
1200                                 */
1201                                 el = node.SelectSingleNode ("infinity");
1202
1203                                 // We cannot use the value from CLDR because many broken
1204                                 // .NET serializers (e.g. JSON) use text value of NegativeInfinity
1205                                 // and different value would break interoperability with .NET
1206                                 var inf = GetInfinitySymbol (ci);
1207                                 if (inf != null)
1208                                         ni.InfinitySymbol = inf;
1209                                 else if (el != null && el.InnerText != "∞") {
1210                                         ni.InfinitySymbol = el.InnerText;
1211                                 }
1212
1213                                 el = node.SelectSingleNode ("perMille");
1214                                 if (el != null)
1215                                         ni.PerMilleSymbol = el.InnerText;
1216
1217                                 el = node.SelectSingleNode ("nan");
1218                                 if (el != null)
1219                                         ni.NaNSymbol = el.InnerText;
1220
1221                                 el = node.SelectSingleNode ("percentSign");
1222                                 if (el != null)
1223                                         ni.PercentSymbol = el.InnerText;
1224
1225                         }
1226                 }
1227
1228                 static void ToLower (string[] values)
1229                 {
1230                         if (values == null)
1231                                 return;
1232
1233                         for (int i = 0; i < values.Length; ++i) {
1234                                 if (values [i] == null)
1235                                         continue;
1236
1237                                 values [i] = values [i].ToLower ();
1238                         }
1239                 }
1240
1241                 string GetInfinitySymbol (CultureInfoEntry ci)
1242                 {
1243                         // TODO: Add more
1244                         switch (ci.TwoLetterISOLanguageName) {
1245                                 case "ca":
1246                                         return "Infinit";
1247                                 case "cs":
1248                                 case "sk":
1249                                         return "+nekonečno";
1250                                 case "de":
1251                                         return "+unendlich";
1252                                 case "el":
1253                                         return "Άπειρο";
1254                                 case "es":
1255                                 case "gl":
1256                                         return "Infinito";
1257                                 case "it":
1258                                 case "pt":
1259                                         return "+Infinito";
1260                                 case "nl":
1261                                         return "oneindig";
1262                                 case "fr":
1263                                 case "tzm":
1264                                         return "+Infini";
1265                                 case "pl":
1266                                         return "+nieskończoność";
1267                                 case "ru":
1268                                 case "tg":
1269                                         return "бесконечность";
1270                                 case "sl":
1271                                         return "neskončnost";
1272                                 case "rm":
1273                                         return "+infinit";
1274                                 case "lv":
1275                                         return "bezgalība";
1276                                 case "lt":
1277                                         return "begalybė";
1278                                 case "eu":
1279                                         return "Infinitu";
1280                         }
1281
1282                         return null;
1283                 }
1284
1285                 static string ConvertDatePatternFormat (string format)
1286                 {
1287                         //
1288                         // LDMR uses different characters for some fields
1289                         // http://unicode.org/reports/tr35/#Date_Format_Patterns
1290                         //
1291                         format = format.Replace ("EEEE", "dddd"); // The full name of the day of the week
1292                         format = format.Replace ("LLLL", "MMMM"); // The full month name
1293
1294                         if (format.EndsWith (" y", StringComparison.Ordinal))
1295                                 format += "yyy";
1296
1297                         return format;
1298                 }
1299
1300                 static string ConvertTimePatternFormat (string format)
1301                 {
1302                         format = format.Replace ("a", "tt"); // AM or PM
1303                         return format;
1304                 }
1305
1306                 static void ProcessAllNodes (XmlNodeList list, IList<string> values, Action<IList<string>, string, string> convertor)
1307                 {
1308                         foreach (XmlNode entry in list) {
1309                                 var index = entry.Attributes["type"].Value;
1310                                 var value = entry.InnerText;
1311                                 convertor (values, index, value);
1312                         }
1313                 }
1314
1315                 // All text indexes are 1-based
1316                 static void AddOrReplaceValue (IList<string> list, string oneBasedIndex, string value)
1317                 {
1318                         int index = int.Parse (oneBasedIndex);
1319                         AddOrReplaceValue (list, index - 1, value);
1320                 }
1321
1322                 static readonly string[] day_types = new string[] { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
1323
1324                 static void AddOrReplaceDayValue (IList<string> list, string dayType, string value)
1325                 {
1326                         int index = Array.IndexOf (day_types, dayType);
1327                         AddOrReplaceValue (list, index, value);
1328                 }
1329
1330                 static void AddOrReplaceValue (IList<string> list, int index, string value)
1331                 {
1332                         if (list.Count <= index)
1333                                 ((List<string>) list).AddRange (new string[index - list.Count + 1]);
1334
1335                         list[index] = value;
1336                 }
1337
1338                 sealed class LcidComparer : IComparer<CultureInfoEntry>
1339                 {
1340                         public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1341                         {
1342                                 return x.LCID.CompareTo (y.LCID);
1343                         }
1344                 }
1345
1346                 sealed class ExportNameComparer : IComparer<CultureInfoEntry>
1347                 {
1348                         public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1349                         {
1350                                 return String.Compare (x.GetExportName (), y.GetExportName (), StringComparison.OrdinalIgnoreCase);
1351                         }
1352                 }
1353
1354                 class RegionComparer : IComparer<RegionInfoEntry>
1355                 {
1356                         public int Compare (RegionInfoEntry x, RegionInfoEntry y)
1357                         {
1358                                 return x.TwoLetterISORegionName.CompareTo (y.TwoLetterISORegionName);
1359                         }
1360                 }
1361         }
1362 }