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}", "PercentDecimalDigits", nf
.PercentDecimalDigits
);
178 writer
.WriteLine ("{0}: {1}", "PercentDecimalSeparator", nf
.PercentDecimalSeparator
);
179 writer
.WriteLine ("{0}: {1}", "PercentGroupSeparator", nf
.PercentGroupSeparator
);
180 Dump (writer
, nf
.PercentGroupSizes
, "PercentGroupSizes", true);
181 writer
.WriteLine ("{0}: {1}", "PercentNegativePattern", nf
.PercentNegativePattern
);
182 writer
.WriteLine ("{0}: {1}", "PercentPositivePattern", nf
.PercentPositivePattern
);
183 writer
.WriteLine ("{0}: {1}", "PercentSymbol", nf
.PercentSymbol
);
184 writer
.WriteLine ("{0}: {1}", "PerMilleSymbol", nf
.PerMilleSymbol
);
185 writer
.WriteLine ("{0}: {1}", "PositiveInfinitySymbol", nf
.PositiveInfinitySymbol
);
186 writer
.WriteLine ("{0}: {1}", "PositiveSign", nf
.PositiveSign
);
188 if (c
.RegionInfoEntry
!= null) {
189 var ri
= c
.RegionInfoEntry
;
190 writer
.WriteLine ("-- RegionInfo --");
191 writer
.WriteLine ("{0}: {1}", "CurrencyEnglishName", ri
.CurrencyEnglishName
);
192 writer
.WriteLine ("{0}: {1}", "CurrencyNativeName", ri
.CurrencyNativeName
);
193 writer
.WriteLine ("{0}: {1}", "CurrencySymbol", ri
.CurrencySymbol
);
194 writer
.WriteLine ("{0}: {1}", "DisplayName", ri
.DisplayName
);
195 writer
.WriteLine ("{0}: {1}", "EnglishName", ri
.EnglishName
);
196 writer
.WriteLine ("{0}: {1}", "GeoId", ri
.GeoId
);
197 writer
.WriteLine ("{0}: {1}", "IsMetric", ri
.IsMetric
);
198 writer
.WriteLine ("{0}: {1}", "ISOCurrencySymbol", ri
.ISOCurrencySymbol
);
199 writer
.WriteLine ("{0}: {1}", "Name", ri
.Name
);
200 writer
.WriteLine ("{0}: {1}", "NativeName", ri
.NativeName
);
201 writer
.WriteLine ("{0}: {1}", "ThreeLetterISORegionName", ri
.ThreeLetterISORegionName
);
202 writer
.WriteLine ("{0}: {1}", "ThreeLetterWindowsRegionName", ri
.ThreeLetterWindowsRegionName
);
203 writer
.WriteLine ("{0}: {1}", "TwoLetterISORegionName", ri
.TwoLetterISORegionName
);
210 static Type
GetCalendarType (CalendarType ct
)
213 case CalendarType
.Gregorian
:
214 return typeof (GregorianCalendar
);
215 case CalendarType
.HijriCalendar
:
216 return typeof (HijriCalendar
);
217 case CalendarType
.ThaiBuddhist
:
218 return typeof (ThaiBuddhistCalendar
);
219 case CalendarType
.UmAlQuraCalendar
:
220 return typeof (UmAlQuraCalendar
);
222 throw new NotImplementedException ();
226 static void Dump
<T
> (TextWriter tw
, IList
<T
> values
, string name
, bool stopOnNull
= false) where T
: class
231 for (int i
= 0; i
< values
.Count
; ++i
) {
234 if (stopOnNull
&& v
== null)
248 Regex locales_regex
= null;
250 locales_regex
= new Regex (Locales
);
252 cultures
= new List
<CultureInfoEntry
> ();
253 var regions
= new List
<RegionInfoEntry
> ();
256 var supplemental
= GetXmlDocument (Path
.Combine (data_root
, "supplemental", "supplementalData.xml"));
258 // Read currencies info
259 region_currency
= new Dictionary
<string, string> (StringComparer
.OrdinalIgnoreCase
);
260 foreach (XmlNode entry
in supplemental
.SelectNodes ("supplementalData/currencyData/region")) {
261 var child
= entry
.SelectSingleNode ("currency");
262 region_currency
.Add (entry
.Attributes
["iso3166"].Value
, child
.Attributes
["iso4217"].Value
);
266 extra_parent_locales
= new Dictionary
<string, string> (StringComparer
.OrdinalIgnoreCase
);
267 foreach (XmlNode entry
in supplemental
.SelectNodes ("supplementalData/parentLocales/parentLocale")) {
268 var parent
= entry
.Attributes
["parent"].Value
;
270 if (parent
== "root")
273 var locales
= entry
.Attributes
["locales"].Value
;
274 foreach (var locale
in locales
.Split (' '))
275 extra_parent_locales
.Add (locale
, parent
);
278 var lcdids
= GetXmlDocument ("lcids.xml");
279 foreach (XmlNode lcid
in lcdids
.SelectNodes ("lcids/lcid")) {
280 var name
= lcid
.Attributes
["name"].Value
;
282 if (locales_regex
!= null && !locales_regex
.IsMatch (name
))
285 var ci
= new CultureInfoEntry ();
286 ci
.LCID
= lcid
.Attributes
["id"].Value
;
287 ci
.ParentLcid
= lcid
.Attributes
["parent"].Value
;
288 ci
.TwoLetterISOLanguageName
= lcid
.Attributes
["iso2"].Value
;
289 ci
.ThreeLetterISOLanguageName
= lcid
.Attributes
["iso3"].Value
;
290 ci
.ThreeLetterWindowsLanguageName
= lcid
.Attributes
["win"].Value
;
291 ci
.OriginalName
= name
.Replace ('_', '-');
292 ci
.TextInfoEntry
= new TextInfoEntry ();
293 ci
.NumberFormatEntry
= new NumberFormatEntry ();
295 if (!Import (ci
, name
))
301 var doc_english
= GetXmlDocument (Path
.Combine (data_root
, "main", "en.xml"));
304 // Fill all EnglishName values from en.xml language file
306 foreach (var ci
in cultures
) {
307 var el
= doc_english
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", ci
.Language
));
309 ci
.EnglishName
= el
.InnerText
;
312 if (ci
.Script
!= null) {
313 el
= doc_english
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/scripts/script[@type='{0}']", ci
.Script
));
318 if (ci
.Territory
!= null) {
319 el
= doc_english
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci
.Territory
));
324 s
= string.Join (", ", s
, el
.InnerText
);
328 switch (ci
.ThreeLetterWindowsLanguageName
) {
338 ci
.EnglishName
= string.Format ("{0} ({1})", ci
.EnglishName
, s
);
340 // Special case legacy chinese
341 if (ci
.OriginalName
== "zh-CHS" || ci
.OriginalName
== "zh-CHT")
342 ci
.EnglishName
+= " Legacy";
344 // Mono is not localized and supports english only, hence the name will always be same
345 ci
.DisplayName
= ci
.EnglishName
;
349 // Fill culture hierarchy for easier data manipulation
351 foreach (var ci
in cultures
) {
352 foreach (var p
in cultures
.Where (l
=> ci
.LCID
== l
.ParentLcid
)) {
357 currency_fractions
= new Dictionary
<string, string> (StringComparer
.OrdinalIgnoreCase
);
358 foreach (XmlNode entry
in supplemental
.SelectNodes ("supplementalData/currencyData/fractions/info")) {
359 currency_fractions
.Add (entry
.Attributes
["iso4217"].Value
, entry
.Attributes
["digits"].Value
);
362 var territory2dayofweek
= new Dictionary
<string, DayOfWeek
> (StringComparer
.OrdinalIgnoreCase
);
363 foreach (XmlNode entry
in supplemental
.SelectNodes ("supplementalData/weekData/firstDay")) {
366 switch (entry
.Attributes
["day"].Value
) {
368 dow
= DayOfWeek
.Monday
;
371 dow
= DayOfWeek
.Friday
;
374 dow
= DayOfWeek
.Saturday
;
377 dow
= DayOfWeek
.Sunday
;
380 throw new NotImplementedException ();
383 var territories
= entry
.Attributes
["territories"].Value
.Split ();
384 foreach (var t
in territories
)
385 territory2dayofweek
[t
] = dow
;
388 var territory2wr
= new Dictionary
<string, CalendarWeekRule
> (StringComparer
.OrdinalIgnoreCase
);
389 foreach (XmlNode entry
in supplemental
.SelectNodes ("supplementalData/weekData/minDays")) {
390 CalendarWeekRule rule
;
392 switch (entry
.Attributes
["count"].InnerText
) {
394 rule
= CalendarWeekRule
.FirstDay
;
397 rule
= CalendarWeekRule
.FirstFourDayWeek
;
400 throw new NotImplementedException ();
403 var territories
= entry
.Attributes
["territories"].InnerText
.Split ();
404 foreach (var t
in territories
)
405 territory2wr
[t
] = rule
;
409 // Fill all territory speficic data where territory is available
411 var non_metric
= new HashSet
<string> ();
412 foreach (XmlNode entry
in supplemental
.SelectNodes ("supplementalData/measurementData/measurementSystem[@type='US']")) {
413 var territories
= entry
.Attributes
["territories"].InnerText
.Split ();
414 foreach (var t
in territories
)
418 foreach (var ci
in cultures
) {
419 if (ci
.Territory
== null)
423 if (territory2dayofweek
.TryGetValue (ci
.Territory
, out value)) {
424 ci
.DateTimeFormatEntry
.FirstDayOfWeek
= (int) value;
427 CalendarWeekRule rule
;
428 if (territory2wr
.TryGetValue (ci
.Territory
, out rule
)) {
429 ci
.DateTimeFormatEntry
.CalendarWeekRule
= (int) rule
;
432 RegionInfoEntry region
= regions
.Where (l
=> l
.Name
== ci
.Territory
).FirstOrDefault ();
433 if (region
== null) {
434 region
= new RegionInfoEntry () {
435 CurrencySymbol
= ci
.NumberFormatEntry
.CurrencySymbol
,
436 EnglishName
= ci
.EnglishName
,
437 NativeName
= ci
.NativeTerritoryName
,
439 TwoLetterISORegionName
= ci
.Territory
,
440 CurrencyNativeName
= ci
.NativeCurrencyName
443 var tc
= supplemental
.SelectSingleNode (string.Format ("supplementalData/codeMappings/territoryCodes[@type='{0}']", ci
.Territory
));
444 region
.ThreeLetterISORegionName
= tc
.Attributes
["alpha3"].Value
;
445 region
.ThreeLetterWindowsRegionName
= region
.ThreeLetterISORegionName
;
447 var el
= doc_english
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci
.Territory
));
448 region
.EnglishName
= el
.InnerText
;
449 region
.DisplayName
= region
.EnglishName
;
451 region
.ISOCurrencySymbol
= region_currency
[ci
.Territory
];
453 el
= doc_english
.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", region
.ISOCurrencySymbol
));
454 region
.CurrencyEnglishName
= el
.InnerText
;
456 if (non_metric
.Contains (ci
.Territory
))
457 region
.IsMetric
= false;
459 var lcdid_value
= int.Parse (ci
.LCID
.Substring (2), NumberStyles
.HexNumber
);
460 Patterns
.FillValues (lcdid_value
, region
);
461 regions
.Add (region
);
464 string fraction_value
;
465 if (currency_fractions
.TryGetValue (region
.ISOCurrencySymbol
, out fraction_value
)) {
466 ci
.NumberFormatEntry
.CurrencyDecimalDigits
= fraction_value
;
469 ci
.RegionInfoEntry
= region
;
473 // Fill neutral cultures territory data
475 foreach (var ci
in cultures
) {
476 var dtf
= ci
.DateTimeFormatEntry
;
477 if (dtf
.FirstDayOfWeek
== null) {
480 dtf
.FirstDayOfWeek
= (int) DayOfWeek
.Saturday
;
485 dtf
.FirstDayOfWeek
= (int) DayOfWeek
.Sunday
;
492 dtf
.FirstDayOfWeek
= (int) DayOfWeek
.Monday
;
495 List
<int?> all_fdow
= new List
<int?> ();
496 GetAllChildrenValues (ci
, all_fdow
, l
=> l
.DateTimeFormatEntry
.FirstDayOfWeek
);
497 var children
= all_fdow
.Where (l
=> l
!= null).Distinct ().ToList ();
499 if (children
.Count
== 1) {
500 dtf
.FirstDayOfWeek
= children
[0];
501 } else if (children
.Count
== 0) {
502 if (!ci
.HasMissingLocale
)
503 Console
.WriteLine ("No week data for `{0}'", ci
.Name
);
506 dtf
.FirstDayOfWeek
= (int) DayOfWeek
.Sunday
;
508 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
509 // We have to manually disambiguate the correct entry (which is artofficial anyway)
510 throw new ApplicationException (string.Format ("Ambiguous week data for `{0}'", ci
.Name
));
517 if (dtf
.CalendarWeekRule
== null) {
526 dtf
.CalendarWeekRule
= (int) CalendarWeekRule
.FirstDay
;
529 List
<int?> all_cwr
= new List
<int?> ();
530 GetAllChildrenValues (ci
, all_cwr
, l
=> l
.DateTimeFormatEntry
.CalendarWeekRule
);
531 var children
= all_cwr
.Where (l
=> l
!= null).Distinct ().ToList ();
533 if (children
.Count
== 1) {
534 dtf
.CalendarWeekRule
= children
[0];
535 } else if (children
.Count
== 0) {
536 if (!ci
.HasMissingLocale
)
537 Console
.WriteLine ("No calendar week data for `{0}'", ci
.Name
);
540 // Default to FirstDay
541 dtf
.CalendarWeekRule
= (int) CalendarWeekRule
.FirstDay
;
543 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
544 // We have to manually disambiguate the correct entry (which is artofficial anyway)
545 throw new ApplicationException (string.Format ("Ambiguous calendar data for `{0}'", ci
.Name
));
552 var nfe
= ci
.NumberFormatEntry
;
553 if (nfe
.CurrencySymbol
== null) {
556 nfe
.CurrencySymbol
= "ر.س.";
559 nfe
.CurrencySymbol
= "$";
563 nfe
.CurrencySymbol
= "€";
566 nfe
.CurrencySymbol
= "R$";
569 nfe
.CurrencySymbol
= "kr";
572 nfe
.CurrencySymbol
= "RM";
575 nfe
.CurrencySymbol
= "টা";
578 nfe
.CurrencySymbol
= "Дин.";
582 nfe
.CurrencySymbol
= "Din.";
585 nfe
.CurrencySymbol
= "¥";
588 nfe
.CurrencySymbol
= "HK$";
592 var all_currencies
= new List
<string> ();
593 GetAllChildrenValues (ci
, all_currencies
, l
=> l
.NumberFormatEntry
.CurrencySymbol
);
594 var children
= all_currencies
.Where (l
=> l
!= null).Distinct ().ToList ();
596 if (children
.Count
== 1) {
597 nfe
.CurrencySymbol
= children
[0];
598 } else if (children
.Count
== 0) {
599 if (!ci
.HasMissingLocale
)
600 Console
.WriteLine ("No currency data for `{0}'", ci
.Name
);
604 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
605 // We have to manually disambiguate the correct entry (which is artofficial anyway)
606 throw new ApplicationException (string.Format ("Ambiguous currency data for `{0}'", ci
.Name
));
613 if (nfe
.CurrencyDecimalDigits
== null) {
614 var all_digits
= new List
<string> ();
615 GetAllChildrenValues (ci
, all_digits
, l
=> l
.NumberFormatEntry
.CurrencyDecimalDigits
);
616 var children
= all_digits
.Where (l
=> l
!= null).Distinct ().ToList ();
618 if (children
.Count
== 1) {
619 nfe
.CurrencyDecimalDigits
= children
[0];
620 } else if (children
.Count
== 0) {
621 if (!ci
.HasMissingLocale
)
622 Console
.WriteLine ("No currency decimal digits data for `{0}'", ci
.Name
);
624 nfe
.CurrencyDecimalDigits
= "2";
625 } else if (ci
.IsNeutral
) {
626 nfe
.CurrencyDecimalDigits
= "2";
628 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
629 // We have to manually disambiguate the correct entry (which is artofficial anyway)
630 throw new ApplicationException (string.Format ("Ambiguous currency decimal digits data for `{0}'", ci
.Name
));
638 regions
.Sort (new RegionComparer ());
639 for (int i
= 0; i
< regions
.Count
; ++i
)
640 regions
[i
].Index
= i
;
643 * Dump each table individually. Using StringBuilders
644 * because it is easier to debug, should switch to just
645 * writing to streams eventually.
647 using (StreamWriter writer
= new StreamWriter (HeaderFileName
, false, new UTF8Encoding (false, true))) {
648 writer
.NewLine
= "\n";
650 writer
.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
651 writer
.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
652 writer
.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
653 writer
.WriteLine ("\n");
655 writer
.WriteLine ("#define NUM_CULTURE_ENTRIES {0}", cultures
.Count
);
656 writer
.WriteLine ("#define NUM_REGION_ENTRIES {0}", regions
.Count
);
658 writer
.WriteLine ("\n");
660 // Sort the cultures by lcid
661 cultures
.Sort (new LcidComparer ());
663 StringBuilder builder
= new StringBuilder ();
665 int count
= cultures
.Count
;
666 for (int i
= 0; i
< count
; i
++) {
667 CultureInfoEntry ci
= cultures
[i
];
668 if (ci
.DateTimeFormatEntry
== null)
670 ci
.DateTimeFormatEntry
.AppendTableRow (builder
);
671 ci
.DateTimeFormatEntry
.Row
= row
++;
673 builder
.Append (',');
674 builder
.Append ('\n');
677 writer
.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
678 writer
.Write (builder
);
679 writer
.WriteLine ("};\n\n");
681 builder
= new StringBuilder ();
683 for (int i
= 0; i
< count
; i
++) {
684 CultureInfoEntry ci
= cultures
[i
];
685 if (ci
.NumberFormatEntry
== null)
687 ci
.NumberFormatEntry
.AppendTableRow (builder
);
688 ci
.NumberFormatEntry
.Row
= row
++;
690 builder
.Append (',');
691 builder
.Append ('\n');
694 writer
.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
695 writer
.Write (builder
);
696 writer
.WriteLine ("};\n\n");
698 builder
= new StringBuilder ();
700 for (int i
= 0; i
< count
; i
++) {
701 CultureInfoEntry ci
= cultures
[i
];
702 ci
.AppendTableRow (builder
);
705 builder
.Append (',');
706 builder
.Append ('\n');
709 writer
.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
710 writer
.Write (builder
);
711 writer
.WriteLine ("};\n\n");
713 cultures
.Sort (new ExportNameComparer ()); // Sort based on name
714 builder
= new StringBuilder ();
715 for (int i
= 0; i
< count
; i
++) {
716 CultureInfoEntry ci
= cultures
[i
];
717 var name
= ci
.GetExportName ().ToLowerInvariant ();
718 builder
.Append ("\t{" + Entry
.EncodeStringIdx (name
) + ", ");
719 builder
.Append (ci
.Row
+ "}");
721 builder
.Append (',');
723 builder
.AppendFormat ("\t /* {0} */", name
);
724 builder
.Append ('\n');
727 writer
.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
728 writer
.Write (builder
);
729 writer
.WriteLine ("};\n\n");
731 builder
= new StringBuilder ();
733 foreach (RegionInfoEntry r
in regions
) {
734 r
.AppendTableRow (builder
);
735 if (++rcount
!= regions
.Count
)
736 builder
.Append (',');
738 builder
.Append ('\n');
740 writer
.WriteLine ("static const RegionInfoEntry region_entries [] = {");
741 writer
.Write (builder
);
742 writer
.WriteLine ("};\n\n");
744 builder
= new StringBuilder ();
746 foreach (RegionInfoEntry ri
in regions
) {
747 builder
.Append ("\t{" + Entry
.EncodeStringIdx (ri
.TwoLetterISORegionName
) + ", ");
748 builder
.Append (ri
.Index
+ "}");
749 if (++rcount
!= regions
.Count
)
750 builder
.Append (',');
752 builder
.AppendFormat ("\t /* {0} */", ri
.TwoLetterISORegionName
);
753 builder
.Append ('\n');
756 writer
.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
757 writer
.Write (builder
);
758 writer
.WriteLine ("};\n\n");
760 writer
.WriteLine ("static const char locale_strings [] = {");
761 writer
.Write (Entry
.GetStrings ());
762 writer
.WriteLine ("};\n\n");
764 writer
.WriteLine ("#endif\n");
768 static void GetAllChildrenValues
<T
> (CultureInfoEntry entry
, List
<T
> values
, Func
<CultureInfoEntry
, T
> selector
)
770 foreach (var e
in entry
.Children
) {
774 values
.Add (selector (e
));
776 foreach (var e2
in e
.Children
) {
777 GetAllChildrenValues (e2
, values
, selector
);
782 static XmlDocument
GetXmlDocument (string path
)
784 var doc
= new XmlDocument ();
785 doc
.Load (new XmlTextReader (path
) { /*DtdProcessing = DtdProcessing.Ignore*/ }
);
789 bool Import (CultureInfoEntry data
, string locale
)
792 var sep
= locale
.Split ('_');
793 data
.Language
= sep
[0];
795 // CLDR strictly follow ISO names, .NET does not
796 // Replace names where non-iso2 is used, e.g. Norway
797 if (data
.Language
!= data
.TwoLetterISOLanguageName
) {
798 locale
= data
.TwoLetterISOLanguageName
;
799 if (sep
.Length
> 1) {
800 locale
+= string.Join ("_", sep
.Skip (1));
804 // Convert broken Chinese names to correct one
813 locale
= "zh_Hans_CN";
816 locale
= "zh_Hant_HK";
819 locale
= "zh_Hans_SG";
822 locale
= "zh_Hant_TW";
825 locale
= "zh_Hant_MO";
829 sep
= locale
.Split ('_');
831 string full_name
= Path
.Combine (data_root
, "main", locale
+ ".xml");
832 if (!File
.Exists (full_name
)) {
833 Console
.WriteLine ("Missing locale file for `{0}'", locale
);
835 // We could fill default values but that's not as simple as it seems. For instance for non-neutral
836 // cultures the next part could be territory or not.
839 XmlDocument doc
= null;
842 * Locale generation is done in several steps, first we
843 * read the root file which is the base invariant data
844 * then the supplemental root data,
845 * then the language file, the supplemental languages
846 * file then the locale file, then the supplemental
847 * locale file. Values in each descending file can
848 * overwrite previous values.
850 foreach (var part
in sep
) {
858 if (extra_parent_locales
.TryGetValue (fname
, out extra
)) {
859 xml
= GetXmlDocument (Path
.Combine (data_root
, "main", extra
+ ".xml"));
866 xml
= GetXmlDocument (Path
.Combine (data_root
, "main", fname
+ ".xml"));
874 // Extract localized locale name from language xml file. Have to do it after both language and territory are read
876 var el
= doc
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", data
.Language
));
878 data
.NativeName
= el
.InnerText
;
880 if (data
.Territory
!= null) {
881 el
= doc
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", data
.Territory
));
883 // TODO: Should read <localePattern>
884 data
.NativeName
= string.Format ("{0} ({1})", data
.NativeName
, el
.InnerText
);
885 data
.NativeTerritoryName
= el
.InnerText
;
889 // We have territory now we have to run the process again to extract currency symbol
890 if (region_currency
.TryGetValue (data
.Territory
, out currency
)) {
893 var xml
= GetXmlDocument (Path
.Combine (data_root
, "main", "root.xml"));
894 el
= xml
.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency
));
896 data
.NumberFormatEntry
.CurrencySymbol
= el
.InnerText
;
898 foreach (var part
in sep
) {
904 xml
= GetXmlDocument (Path
.Combine (data_root
, "main", fname
+ ".xml"));
905 el
= xml
.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency
));
907 data
.NumberFormatEntry
.CurrencySymbol
= el
.InnerText
;
909 el
= xml
.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", currency
));
911 data
.NativeCurrencyName
= el
.InnerText
;
917 // It looks like it never changes
918 data
.DateTimeFormatEntry
.TimeSeparator
= ":";
920 // TODO: Don't have input data available but most values are 2 with few exceptions for 1 and 3
921 // We don't add 3 as it's for some arabic states only
922 switch (data
.ThreeLetterISOLanguageName
) {
924 data
.NumberFormatEntry
.NumberDecimalDigits
=
925 data
.NumberFormatEntry
.PercentDecimalDigits
= 1;
928 data
.NumberFormatEntry
.NumberDecimalDigits
=
929 data
.NumberFormatEntry
.PercentDecimalDigits
= 2;
933 // TODO: For now we capture only native name for default calendar
934 data
.NativeCalendarNames
[((int) data
.CalendarType
& 0xFF) - 1] = data
.DateTimeFormatEntry
.NativeCalendarName
;
936 var lcdid_value
= int.Parse (data
.LCID
.Substring (2), NumberStyles
.HexNumber
);
937 Patterns
.FillValues (lcdid_value
, data
);
942 void Import (XmlDocument doc
, CultureInfoEntry ci
)
948 // Extract script & teritory
950 el
= doc
.SelectSingleNode ("ldml/identity/script");
952 ci
.Script
= el
.Attributes
["type"].Value
;
954 el
= doc
.SelectSingleNode ("ldml/identity/territory");
956 ci
.Territory
= el
.Attributes
["type"].Value
;
958 var df
= ci
.DateTimeFormatEntry
;
961 // Default calendar is for now always "gregorian"
963 case "th": case "th-TH":
964 calendar
= "buddhist";
965 ci
.CalendarType
= CalendarType
.ThaiBuddhist
; // typeof (ThaiBuddhistCalendar);
967 case "ar": case "ar-SA":
968 calendar
= "islamic";
969 ci
.CalendarType
= CalendarType
.UmAlQuraCalendar
; // typeof (UmAlQuraCalendar);
971 case "ps": case "ps-AF": case "prs": case "prs-AF": case "dv": case "dv-MV":
972 calendar
= "persian";
973 ci
.CalendarType
= CalendarType
.HijriCalendar
; // typeof (HijriCalendar);
976 calendar
= "gregorian";
977 ci
.CalendarType
= CalendarType
.Gregorian
; // typeof (GregorianCalendar);
978 ci
.GregorianCalendarType
= GregorianCalendarTypes
.Localized
;
982 var node
= doc
.SelectSingleNode (string.Format ("ldml/dates/calendars/calendar[@type='{0}']", calendar
));
984 el
= doc
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/types/type[@type='{0}']", calendar
));
986 df
.NativeCalendarName
= el
.InnerText
;
989 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='wide']"/>
990 nodes
= node
.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
991 ProcessAllNodes (nodes
, df
.MonthNames
, AddOrReplaceValue
);
992 nodes
= node
.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='wide']/month");
993 ProcessAllNodes (nodes
, df
.MonthNames
, AddOrReplaceValue
);
995 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='abbreviated']"/>
996 nodes
= node
.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='abbreviated']/month");
997 ProcessAllNodes (nodes
, df
.AbbreviatedMonthNames
, AddOrReplaceValue
);
998 nodes
= node
.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='abbreviated']/month");
999 ProcessAllNodes (nodes
, df
.AbbreviatedMonthNames
, AddOrReplaceValue
);
1001 nodes
= node
.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
1002 if (nodes
!= null) {
1003 ProcessAllNodes (nodes
, df
.MonthGenitiveNames
, AddOrReplaceValue
);
1006 // All values seem to match
1007 Array
.Copy (df
.AbbreviatedMonthNames
, df
.AbbreviatedMonthGenitiveNames
, df
.AbbreviatedMonthNames
.Length
);
1009 nodes
= node
.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='wide']/day");
1010 ProcessAllNodes (nodes
, df
.DayNames
, AddOrReplaceDayValue
);
1012 // Apply global rule first <alias source="locale" path="../../dayContext[@type='format']/dayWidth[@type='abbreviated']"/>
1013 nodes
= node
.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='abbreviated']/day");
1014 ProcessAllNodes (nodes
, df
.AbbreviatedDayNames
, AddOrReplaceDayValue
);
1015 nodes
= node
.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='abbreviated']/day");
1016 ProcessAllNodes (nodes
, df
.AbbreviatedDayNames
, AddOrReplaceDayValue
);
1018 // TODO: This is not really ShortestDayNames as .NET uses it
1019 // Apply global rules first <alias source="locale" path="../../dayContext[@type='stand-alone']/dayWidth[@type='narrow']"/>
1020 nodes
= node
.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='narrow']/day");
1021 ProcessAllNodes (nodes
, df
.ShortestDayNames
, AddOrReplaceDayValue
);
1022 nodes
= node
.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='narrow']/day");
1023 ProcessAllNodes (nodes
, df
.ShortestDayNames
, AddOrReplaceDayValue
);
1025 Cannot really be used it's too different to .NET and most app rely on it
1027 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='full']/dateFormat/pattern");
1029 df.LongDatePattern = ConvertDatePatternFormat (el.InnerText);
1031 // Medium is our short
1032 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='medium']/dateFormat/pattern");
1034 df.ShortDatePattern = ConvertDatePatternFormat (el.InnerText);
1036 // Medium is our Long
1037 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='medium']/timeFormat/pattern");
1039 df.LongTimePattern = ConvertTimePatternFormat (el.InnerText);
1041 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='short']/timeFormat/pattern");
1043 df.ShortTimePattern = ConvertTimePatternFormat (el.InnerText);
1045 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='yyyyMMMM']");
1047 df.YearMonthPattern = ConvertDatePatternFormat (el.InnerText);
1049 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='MMMMdd']");
1051 df.MonthDayPattern = ConvertDatePatternFormat (el.InnerText);
1053 el
= node
.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='am']");
1055 // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1056 el
= node
.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='am']");
1059 df
.AMDesignator
= el
.InnerText
;
1061 el
= node
.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='pm']");
1063 // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1064 el
= node
.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='pm']");
1068 df
.PMDesignator
= el
.InnerText
;
1071 var ni
= ci
.NumberFormatEntry
;
1073 node
= doc
.SelectSingleNode ("ldml/numbers/symbols");
1075 el
= node
.SelectSingleNode ("decimal");
1077 ni
.NumberDecimalSeparator
=
1078 ni
.PercentDecimalSeparator
= el
.InnerText
;
1081 el
= node
.SelectSingleNode ("plusSign");
1083 ni
.PositiveSign
= el
.InnerText
;
1085 el
= node
.SelectSingleNode ("minusSign");
1087 // CLDR uses unicode negative sign for some culture (e.g sv, is, lt, don't kwnow why) but .net always
1088 // uses simple - sign
1089 if (el
.InnerText
== "\u2212")
1090 ni
.NegativeSign
= "-";
1092 ni
.NegativeSign
= el
.InnerText
;
1095 el
= node
.SelectSingleNode ("infinity");
1097 // We cannot use the value from CLDR because many broken
1098 // .NET serializers (e.g. JSON) use text value of NegativeInfinity
1099 // and different value would break interoperability with .NET
1100 var inf
= GetInfinitySymbol (ci
);
1102 ni
.InfinitySymbol
= inf
;
1103 else if (el
!= null && el
.InnerText
!= "∞") {
1104 ni
.InfinitySymbol
= el
.InnerText
;
1107 el
= node
.SelectSingleNode ("perMille");
1109 ni
.PerMilleSymbol
= el
.InnerText
;
1111 el
= node
.SelectSingleNode ("nan");
1113 ni
.NaNSymbol
= el
.InnerText
;
1115 el
= node
.SelectSingleNode ("percentSign");
1117 ni
.PercentSymbol
= el
.InnerText
;
1119 el
= node
.SelectSingleNode ("group");
1121 ni
.NumberGroupSeparator
=
1122 ni
.PercentGroupSeparator
=
1123 ni
.CurrencyGroupSeparator
= el
.InnerText
;
1128 string GetInfinitySymbol (CultureInfoEntry ci
)
1131 switch (ci
.TwoLetterISOLanguageName
) {
1136 return "+nekonečno";
1138 return "+unendlich";
1153 return "+nieskończoność";
1156 return "бесконечность";
1158 return "neskončnost";
1172 static string ConvertDatePatternFormat (string format
)
1175 // LDMR uses different characters for some fields
1176 // http://unicode.org/reports/tr35/#Date_Format_Patterns
1178 format
= format
.Replace ("EEEE", "dddd"); // The full name of the day of the week
1179 format
= format
.Replace ("LLLL", "MMMM"); // The full month name
1181 if (format
.EndsWith (" y", StringComparison
.Ordinal
))
1187 static string ConvertTimePatternFormat (string format
)
1189 format
= format
.Replace ("a", "tt"); // AM or PM
1193 static void ProcessAllNodes (XmlNodeList list
, IList
<string> values
, Action
<IList
<string>, string, string> convertor
)
1195 foreach (XmlNode entry
in list
) {
1196 var index
= entry
.Attributes
["type"].Value
;
1197 var value = entry
.InnerText
;
1198 convertor (values
, index
, value);
1202 // All text indexes are 1-based
1203 static void AddOrReplaceValue (IList
<string> list
, string oneBasedIndex
, string value)
1205 int index
= int.Parse (oneBasedIndex
);
1206 AddOrReplaceValue (list
, index
- 1, value);
1209 static readonly string[] day_types
= new string[] { "sun", "mon", "tue", "wed", "thu", "fri", "sat" }
;
1211 static void AddOrReplaceDayValue (IList
<string> list
, string dayType
, string value)
1213 int index
= Array
.IndexOf (day_types
, dayType
);
1214 AddOrReplaceValue (list
, index
, value);
1217 static void AddOrReplaceValue (IList
<string> list
, int index
, string value)
1219 if (list
.Count
<= index
)
1220 ((List
<string>) list
).AddRange (new string[index
- list
.Count
+ 1]);
1222 list
[index
] = value;
1225 sealed class LcidComparer
: IComparer
<CultureInfoEntry
>
1227 public int Compare (CultureInfoEntry x
, CultureInfoEntry y
)
1229 return x
.LCID
.CompareTo (y
.LCID
);
1233 sealed class ExportNameComparer
: IComparer
<CultureInfoEntry
>
1235 public int Compare (CultureInfoEntry x
, CultureInfoEntry y
)
1237 return String
.Compare (x
.GetExportName (), y
.GetExportName (), StringComparison
.OrdinalIgnoreCase
);
1241 class RegionComparer
: IComparer
<RegionInfoEntry
>
1243 public int Compare (RegionInfoEntry x
, RegionInfoEntry y
)
1245 return x
.TwoLetterISORegionName
.CompareTo (y
.TwoLetterISORegionName
);