Exception stack trace should be null when not thrown yet
[mono-project.git] / tools / locale-builder / Driver.cs
blobce6eee6680674eb547323ad8b36767a2c7ee5563
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)
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:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
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.
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;
41 namespace Mono.Tools.LocaleBuilder
43 public class Driver
45 static readonly string data_root = Path.Combine ("CLDR", "common");
47 public static void Main (string[] args)
49 Driver d = new Driver ();
50 ParseArgs (args, d);
51 d.Run ();
54 private static void ParseArgs (string[] args, Driver d)
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;
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;
76 // The lang is the language that display names will be displayed in
77 public string Lang
79 get
81 if (lang == null)
82 lang = "en";
83 return lang;
85 set { lang = value; }
88 public string Locales
90 get { return locales; }
91 set { locales = value; }
94 public string HeaderFileName
96 get
98 if (header_name == null)
99 return "culture-info-tables.h";
100 return header_name;
102 set { header_name = value; }
105 public bool OutputCompare { get; set; }
107 void Print ()
109 cultures.Sort ((a, b) => int.Parse (a.LCID.Substring (2), NumberStyles.HexNumber).CompareTo (int.Parse (b.LCID.Substring (2), NumberStyles.HexNumber)));
111 var writer = Console.Out;
113 foreach (var c in cultures) {
114 writer.WriteLine ("Name: {0}, LCID {1}", c.OriginalName, c.LCID);
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));
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);
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);
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);
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);
202 writer.WriteLine ();
206 static Type GetCalendarType (CalendarType ct)
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 ();
222 static void Dump<T> (TextWriter tw, IList<T> values, string name, bool stopOnNull = false) where T : class
224 tw.Write (name);
225 tw.Write (": ");
227 for (int i = 0; i < values.Count; ++i) {
228 var v = values[i];
230 if (stopOnNull && v == null)
231 break;
233 if (i > 0)
234 tw.Write (", ");
236 tw.Write (v);
239 tw.WriteLine ();
242 void Run ()
244 Regex locales_regex = null;
245 if (Locales != null)
246 locales_regex = new Regex (Locales);
248 cultures = new List<CultureInfoEntry> ();
249 var regions = new List<RegionInfoEntry> ();
252 var supplemental = GetXmlDocument (Path.Combine (data_root, "supplemental", "supplementalData.xml"));
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);
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;
266 if (parent == "root")
267 continue;
269 var locales = entry.Attributes["locales"].Value;
270 foreach (var locale in locales.Split (' '))
271 extra_parent_locales.Add (locale, parent);
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"
301 var lcdids = GetXmlDocument ("lcids.xml");
302 foreach (XmlNode lcid in lcdids.SelectNodes ("lcids/lcid")) {
303 var name = lcid.Attributes["name"].Value;
305 if (locales_regex != null && !locales_regex.IsMatch (name))
306 continue;
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 ();
318 if (!Import (ci, name)) {
319 if (knownLCIDs.Contains (name)) {
320 Console.WriteLine ($"Missing previously available culture `{ name }' data");
321 return;
324 continue;
328 if (!knownLCIDs.Contains (name)) {
329 Console.WriteLine ($"New culture `{ name }' data available");
330 return;
333 cultures.Add (ci);
336 var doc_english = GetXmlDocument (Path.Combine (data_root, "main", "en.xml"));
339 // Fill all EnglishName values from en.xml language file
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;
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;
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);
363 switch (ci.ThreeLetterWindowsLanguageName) {
364 case "CHT":
365 s = "Traditional";
366 break;
367 case "CHS":
368 s = "Simplified";
369 break;
372 if (s != null)
373 ci.EnglishName = string.Format ("{0} ({1})", ci.EnglishName, s);
375 // Special case legacy chinese
376 if (ci.OriginalName == "zh-CHS" || ci.OriginalName == "zh-CHT")
377 ci.EnglishName += " Legacy";
379 // Mono is not localized and supports english only, hence the name will always be same
380 ci.DisplayName = ci.EnglishName;
384 // Fill culture hierarchy for easier data manipulation
386 foreach (var ci in cultures) {
387 foreach (var p in cultures.Where (l => ci.LCID == l.ParentLcid)) {
388 ci.Children.Add (p);
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);
397 var territory2dayofweek = new Dictionary<string, DayOfWeek> (StringComparer.OrdinalIgnoreCase);
398 foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/firstDay")) {
400 if (entry.Attributes ["alt"] != null)
401 continue;
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 ();
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;
427 territory2dayofweek.Add (tr, dow);
431 var territory2wr = new Dictionary<string, CalendarWeekRule> (StringComparer.OrdinalIgnoreCase);
432 foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/minDays")) {
433 CalendarWeekRule rule;
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 ();
446 var territories = entry.Attributes["territories"].InnerText.Split ();
447 foreach (var t in territories)
448 territory2wr[t] = rule;
452 // Fill all territory speficic data where territory is available
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);
461 foreach (var ci in cultures) {
462 if (ci.Territory == null)
463 continue;
465 DayOfWeek value;
466 if (territory2dayofweek.TryGetValue (ci.Territory, out value)) {
467 ci.DateTimeFormatEntry.FirstDayOfWeek = (int) value;
470 CalendarWeekRule rule;
471 if (territory2wr.TryGetValue (ci.Territory, out rule)) {
472 ci.DateTimeFormatEntry.CalendarWeekRule = (int) rule;
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
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;
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;
494 string curr;
495 if (!region_currency.TryGetValue (ci.Territory, out curr))
496 curr = "---";
497 region.ISOCurrencySymbol = curr;
499 el = doc_english.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", region.ISOCurrencySymbol));
500 region.CurrencyEnglishName = el?.InnerText ?? "---";
502 if (non_metric.Contains (ci.Territory))
503 region.IsMetric = false;
505 var lcdid_value = int.Parse (ci.LCID.Substring (2), NumberStyles.HexNumber);
506 Patterns.FillValues (lcdid_value, region);
507 regions.Add (region);
510 string fraction_value;
511 if (currency_fractions.TryGetValue (region.ISOCurrencySymbol, out fraction_value)) {
512 ci.NumberFormatEntry.CurrencyDecimalDigits = fraction_value;
515 ci.RegionInfoEntry = region;
519 // Fill neutral cultures territory data
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 ();
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);
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));
560 break;
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 ();
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);
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));
595 break;
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 ();
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);
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)));
686 break;
691 if (OutputCompare)
692 Print ();
694 regions.Sort (new RegionComparer ());
695 for (int i = 0; i < regions.Count; ++i)
696 regions[i].Index = i;
699 * Dump each table individually. Using StringBuilders
700 * because it is easier to debug, should switch to just
701 * writing to streams eventually.
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");
711 writer.WriteLine ("#define NUM_CULTURE_ENTRIES {0}", cultures.Count);
712 writer.WriteLine ("#define NUM_REGION_ENTRIES {0}", regions.Count);
714 writer.WriteLine ("\n");
716 // Sort the cultures by lcid
717 cultures.Sort (new LcidComparer ());
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');
733 writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
734 writer.Write (builder);
735 writer.WriteLine ("};\n\n");
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');
750 writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
751 writer.Write (builder);
752 writer.WriteLine ("};\n\n");
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');
765 writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
766 writer.Write (builder);
767 writer.WriteLine ("};\n\n");
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 (',');
779 builder.AppendFormat ("\t /* {0} */", name);
780 builder.Append ('\n');
783 writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
784 writer.Write (builder);
785 writer.WriteLine ("};\n\n");
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 (',');
794 builder.Append ('\n');
796 writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
797 writer.Write (builder);
798 writer.WriteLine ("};\n\n");
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 (',');
808 builder.AppendFormat ("\t /* {0} */", ri.TwoLetterISORegionName);
809 builder.Append ('\n');
812 writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
813 writer.Write (builder);
814 writer.WriteLine ("};\n\n");
816 writer.WriteLine ("static const char locale_strings [] = {");
817 writer.Write (Entry.General.GetStrings ());
818 writer.WriteLine ("};\n\n");
820 writer.WriteLine ("static const char patterns [] = {");
821 writer.Write (Entry.Patterns.GetStrings ());
822 writer.WriteLine ("};\n\n");
824 writer.WriteLine ("static const char datetime_strings [] = {");
825 writer.Write (Entry.DateTimeStrings.GetStrings ());
826 writer.WriteLine ("};\n\n");
828 writer.WriteLine ("#endif\n");
832 static void GetAllChildrenValues<T> (CultureInfoEntry entry, List<T> values, Func<CultureInfoEntry, T> selector)
834 foreach (var e in entry.Children) {
835 if (e == entry)
836 continue;
838 values.Add (selector (e));
840 foreach (var e2 in e.Children) {
841 GetAllChildrenValues (e2, values, selector);
846 static XmlDocument GetXmlDocument (string path)
848 var doc = new XmlDocument ();
849 doc.Load (new XmlTextReader (path) { /*DtdProcessing = DtdProcessing.Ignore*/ } );
850 return doc;
853 bool Import (CultureInfoEntry data, string locale)
855 string fname = null;
856 var sep = locale.Split ('_');
857 data.Language = sep[0];
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));
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;
893 sep = locale.Split ('_');
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);
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;
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.
914 foreach (var part in sep) {
915 if (fname != null)
916 fname += "_";
918 fname += part;
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;
927 Import (xml, data);
930 xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
931 if (doc == null)
932 doc = xml;
934 Import (xml, data);
938 // Extract localized locale name from language xml file. Have to do it after both language and territory are read
940 var el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", data.Language));
941 if (el != null)
942 data.NativeName = el.InnerText;
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;
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;
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;
962 foreach (var part in sep) {
963 if (fname != null)
964 fname += "_";
966 fname += part;
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;
973 el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", currency));
974 if (el != null)
975 data.NativeCurrencyName = el.InnerText;
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;
992 // TODO: For now we capture only native name for default calendar
993 data.NativeCalendarNames[((int) data.CalendarType & 0xFF) - 1] = data.DateTimeFormatEntry.NativeCalendarName;
995 var lcdid_value = int.Parse (data.LCID.Substring (2), NumberStyles.HexNumber);
996 Patterns.FillValues (lcdid_value, data);
998 return true;
1001 void Import (XmlDocument doc, CultureInfoEntry ci)
1003 XmlNodeList nodes;
1004 XmlNode el;
1007 // Extract script & teritory
1009 el = doc.SelectSingleNode ("ldml/identity/script");
1010 if (el != null)
1011 ci.Script = el.Attributes["type"].Value;
1013 el = doc.SelectSingleNode ("ldml/identity/territory");
1014 if (el != null)
1015 ci.Territory = el.Attributes["type"].Value;
1017 var df = ci.DateTimeFormatEntry;
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;
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;
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);
1054 if (df.MonthNames != null) {
1055 if (ci.Name == "sv" || ci.Name == "sv-SE") {
1056 ToLower (df.MonthNames);
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);
1070 if (df.AbbreviatedMonthNames != null) {
1071 if (ci.Name == "sv" || ci.Name == "sv-SE") {
1072 ToLower (df.AbbreviatedMonthNames);
1076 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
1077 if (nodes != null) {
1078 ProcessAllNodes (nodes, df.MonthGenitiveNames, AddOrReplaceValue);
1081 // All values seem to match
1082 Array.Copy (df.AbbreviatedMonthNames, df.AbbreviatedMonthGenitiveNames, df.AbbreviatedMonthNames.Length);
1084 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='wide']/day");
1085 ProcessAllNodes (nodes, df.DayNames, AddOrReplaceDayValue);
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);
1093 if (df.AbbreviatedDayNames != null) {
1094 if (ci.Name == "sv" || ci.Name == "sv-SE") {
1095 ToLower (df.AbbreviatedDayNames);
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);
1106 Cannot really be used it's too different to .NET and most app rely on it
1108 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='full']/dateFormat/pattern");
1109 if (el != null)
1110 df.LongDatePattern = ConvertDatePatternFormat (el.InnerText);
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);
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);
1122 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='short']/timeFormat/pattern");
1123 if (el != null)
1124 df.ShortTimePattern = ConvertTimePatternFormat (el.InnerText);
1126 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='yyyyMMMM']");
1127 if (el != null)
1128 df.YearMonthPattern = ConvertDatePatternFormat (el.InnerText);
1130 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='MMMMdd']");
1131 if (el != null)
1132 df.MonthDayPattern = ConvertDatePatternFormat (el.InnerText);
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']");
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 case "ko":
1148 case "ko-KP":
1149 case "ko-KR":
1150 df.AMDesignator = "오전";
1151 break;
1152 default:
1153 if (el != null)
1154 df.AMDesignator = el.InnerText;
1155 break;
1158 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='pm']");
1159 if (el == null)
1160 // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1161 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='pm']");
1163 switch (ci.Name) {
1164 case "en-AU":
1165 df.PMDesignator = "PM";
1166 break;
1167 case "en-NZ":
1168 df.PMDesignator = "p.m.";
1169 break;
1170 case "ko":
1171 case "ko-KP":
1172 case "ko-KR":
1173 df.PMDesignator = "오후";
1174 break;
1175 default:
1176 if (el != null)
1177 df.PMDesignator = el.InnerText;
1178 break;
1182 var ni = ci.NumberFormatEntry;
1184 node = doc.SelectSingleNode ("ldml/numbers/symbols");
1185 if (node != null) {
1186 el = node.SelectSingleNode ("plusSign");
1187 if (el != null)
1188 ni.PositiveSign = el.InnerText;
1190 // CLDR uses unicode negative sign for some culture (e.g sv, is, lt, don't kwnow why) but .NET always
1191 // uses simple "-" sign and what is worse the parsing code cannot deal with non-ASCII values
1192 ni.NegativeSign = "-";
1195 el = node.SelectSingleNode ("minusSign");
1196 if (el != null) {
1197 switch (el.InnerText) {
1198 case "\u2212":
1199 case "\u200F\u002D": // Remove any right-to-left mark characters
1200 case "\u200E\u002D":
1201 case "\u061C\u2212":
1202 case "\u200F\u2212":
1203 ni.NegativeSign = "-";
1204 break;
1205 default:
1206 ni.NegativeSign = el.InnerText;
1207 break;
1211 el = node.SelectSingleNode ("infinity");
1213 // We cannot use the value from CLDR because many broken
1214 // .NET serializers (e.g. JSON) use text value of NegativeInfinity
1215 // and different value would break interoperability with .NET
1216 var inf = GetInfinitySymbol (ci);
1217 if (inf != null)
1218 ni.InfinitySymbol = inf;
1219 else if (el != null && el.InnerText != "∞") {
1220 ni.InfinitySymbol = el.InnerText;
1223 el = node.SelectSingleNode ("perMille");
1224 if (el != null)
1225 ni.PerMilleSymbol = el.InnerText;
1227 el = node.SelectSingleNode ("nan");
1228 if (el != null)
1229 ni.NaNSymbol = el.InnerText;
1231 el = node.SelectSingleNode ("percentSign");
1232 if (el != null)
1233 ni.PercentSymbol = el.InnerText;
1238 static void ToLower (string[] values)
1240 if (values == null)
1241 return;
1243 for (int i = 0; i < values.Length; ++i) {
1244 if (values [i] == null)
1245 continue;
1247 values [i] = values [i].ToLower ();
1251 string GetInfinitySymbol (CultureInfoEntry ci)
1253 // TODO: Add more
1254 switch (ci.TwoLetterISOLanguageName) {
1255 case "ca":
1256 return "Infinit";
1257 case "cs":
1258 case "sk":
1259 return "+nekonečno";
1260 case "de":
1261 return "+unendlich";
1262 case "el":
1263 return "Άπειρο";
1264 case "es":
1265 case "gl":
1266 return "Infinito";
1267 case "it":
1268 case "pt":
1269 return "+Infinito";
1270 case "nl":
1271 return "oneindig";
1272 case "fr":
1273 case "tzm":
1274 return "+Infini";
1275 case "pl":
1276 return "+nieskończoność";
1277 case "ru":
1278 case "tg":
1279 return "бесконечность";
1280 case "sl":
1281 return "neskončnost";
1282 case "rm":
1283 return "+infinit";
1284 case "lv":
1285 return "bezgalība";
1286 case "lt":
1287 return "begalybė";
1288 case "eu":
1289 return "Infinitu";
1292 return null;
1295 static string ConvertDatePatternFormat (string format)
1298 // LDMR uses different characters for some fields
1299 // http://unicode.org/reports/tr35/#Date_Format_Patterns
1301 format = format.Replace ("EEEE", "dddd"); // The full name of the day of the week
1302 format = format.Replace ("LLLL", "MMMM"); // The full month name
1304 if (format.EndsWith (" y", StringComparison.Ordinal))
1305 format += "yyy";
1307 return format;
1310 static string ConvertTimePatternFormat (string format)
1312 format = format.Replace ("a", "tt"); // AM or PM
1313 return format;
1316 static void ProcessAllNodes (XmlNodeList list, IList<string> values, Action<IList<string>, string, string> convertor)
1318 foreach (XmlNode entry in list) {
1319 var index = entry.Attributes["type"].Value;
1320 var value = entry.InnerText;
1321 convertor (values, index, value);
1325 // All text indexes are 1-based
1326 static void AddOrReplaceValue (IList<string> list, string oneBasedIndex, string value)
1328 int index = int.Parse (oneBasedIndex);
1329 AddOrReplaceValue (list, index - 1, value);
1332 static readonly string[] day_types = new string[] { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
1334 static void AddOrReplaceDayValue (IList<string> list, string dayType, string value)
1336 int index = Array.IndexOf (day_types, dayType);
1337 AddOrReplaceValue (list, index, value);
1340 static void AddOrReplaceValue (IList<string> list, int index, string value)
1342 if (list.Count <= index)
1343 ((List<string>) list).AddRange (new string[index - list.Count + 1]);
1345 list[index] = value;
1348 sealed class LcidComparer : IComparer<CultureInfoEntry>
1350 public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1352 return x.LCID.CompareTo (y.LCID);
1356 sealed class ExportNameComparer : IComparer<CultureInfoEntry>
1358 public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1360 return String.Compare (x.GetExportName (), y.GetExportName (), StringComparison.OrdinalIgnoreCase);
1364 class RegionComparer : IComparer<RegionInfoEntry>
1366 public int Compare (RegionInfoEntry x, RegionInfoEntry y)
1368 return x.TwoLetterISORegionName.CompareTo (y.TwoLetterISORegionName);