5 // Jackson Harper (jackson@ximian.com)
6 // Atsushi Enomoto (atsushi@ximian.com)
7 // Marek Safar <marek.safar@gmail.com>
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.
36 using System
.Globalization
;
37 using System
.Text
.RegularExpressions
;
38 using System
.Collections
.Generic
;
41 namespace Mono
.Tools
.LocaleBuilder
45 static readonly string data_root
= Path
.Combine ("CLDR", "common");
47 public static void Main (string[] args
)
49 Driver d
= new Driver ();
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
)
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;
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
90 get { return locales; }
91 set { locales = value; }
94 public string HeaderFileName
98 if (header_name
== null)
99 return "culture-info-tables.h";
102 set { header_name = value; }
105 public bool OutputCompare { get; set; }
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
);
206 static Type
GetCalendarType (CalendarType 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
);
218 throw new NotImplementedException ();
222 static void Dump
<T
> (TextWriter tw
, IList
<T
> values
, string name
, bool stopOnNull
= false) where T
: class
227 for (int i
= 0; i
< values
.Count
; ++i
) {
230 if (stopOnNull
&& v
== null)
244 Regex locales_regex
= 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
);
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")
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
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
))
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");
328 if (!knownLCIDs
.Contains (name
)) {
329 Console
.WriteLine ($"New culture `{ name }' data available");
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
));
344 ci
.EnglishName
= el
.InnerText
;
347 if (ci
.Script
!= null) {
348 el
= doc_english
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/scripts/script[@type='{0}']", ci
.Script
));
353 if (ci
.Territory
!= null) {
354 el
= doc_english
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci
.Territory
));
359 s
= string.Join (", ", s
, el
.InnerText
);
363 switch (ci
.ThreeLetterWindowsLanguageName
) {
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
)) {
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)
404 switch (entry
.Attributes
["day"].Value
) {
406 dow
= DayOfWeek
.Monday
;
409 dow
= DayOfWeek
.Friday
;
412 dow
= DayOfWeek
.Saturday
;
415 dow
= DayOfWeek
.Sunday
;
418 throw new NotImplementedException ();
421 var territories
= entry
.Attributes
["territories"].Value
.Split (new [] { ' ', '\t' }
, StringSplitOptions
.RemoveEmptyEntries
);
422 foreach (var t
in territories
) {
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
) {
437 rule
= CalendarWeekRule
.FirstDay
;
440 rule
= CalendarWeekRule
.FirstFourDayWeek
;
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
)
461 foreach (var ci
in cultures
) {
462 if (ci
.Territory
== null)
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
,
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
;
495 if (!region_currency
.TryGetValue (ci
.Territory
, out 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) {
526 dtf
.FirstDayOfWeek
= (int) DayOfWeek
.Saturday
;
531 dtf
.FirstDayOfWeek
= (int) DayOfWeek
.Sunday
;
539 dtf
.FirstDayOfWeek
= (int) DayOfWeek
.Monday
;
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
);
553 dtf
.FirstDayOfWeek
= (int) DayOfWeek
.Sunday
;
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
));
564 if (dtf
.CalendarWeekRule
== null) {
573 dtf
.CalendarWeekRule
= (int) CalendarWeekRule
.FirstDay
;
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
;
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
));
599 var nfe
= ci
.NumberFormatEntry
;
600 if (nfe
.CurrencySymbol
== null) {
603 nfe
.CurrencySymbol
= "ر.س.";
606 nfe
.CurrencySymbol
= "$";
609 nfe
.CurrencySymbol
= "KM";
616 nfe
.CurrencySymbol
= "€";
619 nfe
.CurrencySymbol
= "kn";
622 nfe
.CurrencySymbol
= "R$";
625 nfe
.CurrencySymbol
= "kr";
628 nfe
.CurrencySymbol
= "RM";
631 nfe
.CurrencySymbol
= "টা";
634 nfe
.CurrencySymbol
= "Дин.";
638 nfe
.CurrencySymbol
= "Din.";
642 nfe
.CurrencySymbol
= "¥";
645 nfe
.CurrencySymbol
= "HK$";
648 nfe
.CurrencySymbol
= "₽";
651 nfe
.CurrencySymbol
= "Rs";
654 nfe
.CurrencySymbol
= "R";
657 nfe
.CurrencySymbol
= "₹";
660 nfe
.CurrencySymbol
= "रु";
663 nfe
.CurrencySymbol
= "Nfk";
666 nfe
.CurrencySymbol
= "RON";
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
);
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
)));
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";
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 ();
721 int count
= cultures
.Count
;
722 for (int i
= 0; i
< count
; i
++) {
723 CultureInfoEntry ci
= cultures
[i
];
724 if (ci
.DateTimeFormatEntry
== null)
726 ci
.DateTimeFormatEntry
.AppendTableRow (builder
);
727 ci
.DateTimeFormatEntry
.Row
= row
++;
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 ();
739 for (int i
= 0; i
< count
; i
++) {
740 CultureInfoEntry ci
= cultures
[i
];
741 if (ci
.NumberFormatEntry
== null)
743 ci
.NumberFormatEntry
.AppendTableRow (builder
);
744 ci
.NumberFormatEntry
.Row
= row
++;
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 ();
756 for (int i
= 0; i
< count
; i
++) {
757 CultureInfoEntry ci
= cultures
[i
];
758 ci
.AppendTableRow (builder
);
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
+ "}");
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 ();
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 ();
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
) {
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*/ }
);
853 bool Import (CultureInfoEntry data
, string locale
)
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
877 locale
= "zh_Hans_CN";
880 locale
= "zh_Hant_HK";
883 locale
= "zh_Hans_SG";
886 locale
= "zh_Hant_TW";
889 locale
= "zh_Hant_MO";
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.
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
) {
922 if (extra_parent_locales
.TryGetValue (fname
, out extra
)) {
923 xml
= GetXmlDocument (Path
.Combine (data_root
, "main", extra
+ ".xml"));
930 xml
= GetXmlDocument (Path
.Combine (data_root
, "main", fname
+ ".xml"));
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
));
942 data
.NativeName
= el
.InnerText
;
944 if (data
.Territory
!= null) {
945 el
= doc
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", data
.Territory
));
947 // TODO: Should read <localePattern>
948 data
.NativeName
= string.Format ("{0} ({1})", data
.NativeName
, el
.InnerText
);
949 data
.NativeTerritoryName
= el
.InnerText
;
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
)) {
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
));
960 data
.NumberFormatEntry
.CurrencySymbol
= el
.InnerText
;
962 foreach (var part
in sep
) {
968 xml
= GetXmlDocument (Path
.Combine (data_root
, "main", fname
+ ".xml"));
969 el
= xml
.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency
));
971 data
.NumberFormatEntry
.CurrencySymbol
= el
.InnerText
;
973 el
= xml
.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", currency
));
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
) {
985 data
.NumberFormatEntry
.NumberDecimalDigits
= 1;
988 data
.NumberFormatEntry
.NumberDecimalDigits
= 2;
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
);
1001 void Import (XmlDocument doc
, CultureInfoEntry ci
)
1007 // Extract script & teritory
1009 el
= doc
.SelectSingleNode ("ldml/identity/script");
1011 ci
.Script
= el
.Attributes
["type"].Value
;
1013 el
= doc
.SelectSingleNode ("ldml/identity/territory");
1015 ci
.Territory
= el
.Attributes
["type"].Value
;
1017 var df
= ci
.DateTimeFormatEntry
;
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);
1026 case "ar": case "ar-SA":
1027 calendar
= "islamic";
1028 ci
.CalendarType
= CalendarType
.UmAlQuraCalendar
; // typeof (UmAlQuraCalendar);
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);
1035 calendar
= "gregorian";
1036 ci
.CalendarType
= CalendarType
.Gregorian
; // typeof (GregorianCalendar);
1037 ci
.GregorianCalendarType
= GregorianCalendarTypes
.Localized
;
1041 var node
= doc
.SelectSingleNode (string.Format ("ldml/dates/calendars/calendar[@type='{0}']", calendar
));
1043 el
= doc
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/types/type[@type='{0}']", calendar
));
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
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");
1110 df.LongDatePattern = ConvertDatePatternFormat (el.InnerText);
1112 // Medium is our short
1113 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='medium']/dateFormat/pattern");
1115 df.ShortDatePattern = ConvertDatePatternFormat (el.InnerText);
1117 // Medium is our Long
1118 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='medium']/timeFormat/pattern");
1120 df.LongTimePattern = ConvertTimePatternFormat (el.InnerText);
1122 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='short']/timeFormat/pattern");
1124 df.ShortTimePattern = ConvertTimePatternFormat (el.InnerText);
1126 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='yyyyMMMM']");
1128 df.YearMonthPattern = ConvertDatePatternFormat (el.InnerText);
1130 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='MMMMdd']");
1132 df.MonthDayPattern = ConvertDatePatternFormat (el.InnerText);
1134 el
= node
.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='am']");
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
1142 df
.AMDesignator
= "AM";
1145 df
.AMDesignator
= "a.m.";
1150 df
.AMDesignator
= "오전";
1154 df
.AMDesignator
= el
.InnerText
;
1158 el
= node
.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='pm']");
1160 // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1161 el
= node
.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='pm']");
1165 df
.PMDesignator
= "PM";
1168 df
.PMDesignator
= "p.m.";
1173 df
.PMDesignator
= "오후";
1177 df
.PMDesignator
= el
.InnerText
;
1182 var ni
= ci
.NumberFormatEntry
;
1184 node
= doc
.SelectSingleNode ("ldml/numbers/symbols");
1186 el
= node
.SelectSingleNode ("plusSign");
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");
1197 switch (el.InnerText) {
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 = "-";
1206 ni.NegativeSign = el.InnerText;
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
);
1218 ni
.InfinitySymbol
= inf
;
1219 else if (el
!= null && el
.InnerText
!= "∞") {
1220 ni
.InfinitySymbol
= el
.InnerText
;
1223 el
= node
.SelectSingleNode ("perMille");
1225 ni
.PerMilleSymbol
= el
.InnerText
;
1227 el
= node
.SelectSingleNode ("nan");
1229 ni
.NaNSymbol
= el
.InnerText
;
1231 el
= node
.SelectSingleNode ("percentSign");
1233 ni
.PercentSymbol
= el
.InnerText
;
1238 static void ToLower (string[] values
)
1243 for (int i
= 0; i
< values
.Length
; ++i
) {
1244 if (values
[i
] == null)
1247 values
[i
] = values
[i
].ToLower ();
1251 string GetInfinitySymbol (CultureInfoEntry ci
)
1254 switch (ci
.TwoLetterISOLanguageName
) {
1259 return "+nekonečno";
1261 return "+unendlich";
1276 return "+nieskończoność";
1279 return "бесконечность";
1281 return "neskončnost";
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
))
1310 static string ConvertTimePatternFormat (string format
)
1312 format
= format
.Replace ("a", "tt"); // AM or PM
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
);