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 string fraction_value
;
433 if (currency_fractions
.TryGetValue (ci
.Territory
, out fraction_value
)) {
434 ci
.NumberFormatEntry
.CurrencyDecimalDigits
= fraction_value
;
437 RegionInfoEntry region
= regions
.Where (l
=> l
.Name
== ci
.Territory
).FirstOrDefault ();
438 if (region
== null) {
439 region
= new RegionInfoEntry () {
440 CurrencySymbol
= ci
.NumberFormatEntry
.CurrencySymbol
,
441 EnglishName
= ci
.EnglishName
,
442 NativeName
= ci
.NativeTerritoryName
,
444 TwoLetterISORegionName
= ci
.Territory
,
445 CurrencyNativeName
= ci
.NativeCurrencyName
448 var tc
= supplemental
.SelectSingleNode (string.Format ("supplementalData/codeMappings/territoryCodes[@type='{0}']", ci
.Territory
));
449 region
.ThreeLetterISORegionName
= tc
.Attributes
["alpha3"].Value
;
450 region
.ThreeLetterWindowsRegionName
= region
.ThreeLetterISORegionName
;
452 var el
= doc_english
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci
.Territory
));
453 region
.EnglishName
= el
.InnerText
;
454 region
.DisplayName
= region
.EnglishName
;
456 region
.ISOCurrencySymbol
= region_currency
[ci
.Territory
];
458 el
= doc_english
.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", region
.ISOCurrencySymbol
));
459 region
.CurrencyEnglishName
= el
.InnerText
;
461 if (non_metric
.Contains (ci
.Territory
))
462 region
.IsMetric
= false;
464 var lcdid_value
= int.Parse (ci
.LCID
.Substring (2), NumberStyles
.HexNumber
);
465 Patterns
.FillValues (lcdid_value
, region
);
466 regions
.Add (region
);
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";
626 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
627 // We have to manually disambiguate the correct entry (which is artofficial anyway)
628 throw new ApplicationException (string.Format ("Ambiguous currency decimal digits data for `{0}'", ci
.Name
));
636 regions
.Sort (new RegionComparer ());
637 for (int i
= 0; i
< regions
.Count
; ++i
)
638 regions
[i
].Index
= i
;
641 * Dump each table individually. Using StringBuilders
642 * because it is easier to debug, should switch to just
643 * writing to streams eventually.
645 using (StreamWriter writer
= new StreamWriter (HeaderFileName
, false, new UTF8Encoding (false, true))) {
646 writer
.NewLine
= "\n";
648 writer
.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
649 writer
.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
650 writer
.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
651 writer
.WriteLine ("\n");
653 writer
.WriteLine ("#define NUM_CULTURE_ENTRIES {0}", cultures
.Count
);
654 writer
.WriteLine ("#define NUM_REGION_ENTRIES {0}", regions
.Count
);
656 writer
.WriteLine ("\n");
658 // Sort the cultures by lcid
659 cultures
.Sort (new LcidComparer ());
661 StringBuilder builder
= new StringBuilder ();
663 int count
= cultures
.Count
;
664 for (int i
= 0; i
< count
; i
++) {
665 CultureInfoEntry ci
= cultures
[i
];
666 if (ci
.DateTimeFormatEntry
== null)
668 ci
.DateTimeFormatEntry
.AppendTableRow (builder
);
669 ci
.DateTimeFormatEntry
.Row
= row
++;
671 builder
.Append (',');
672 builder
.Append ('\n');
675 writer
.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
676 writer
.Write (builder
);
677 writer
.WriteLine ("};\n\n");
679 builder
= new StringBuilder ();
681 for (int i
= 0; i
< count
; i
++) {
682 CultureInfoEntry ci
= cultures
[i
];
683 if (ci
.NumberFormatEntry
== null)
685 ci
.NumberFormatEntry
.AppendTableRow (builder
);
686 ci
.NumberFormatEntry
.Row
= row
++;
688 builder
.Append (',');
689 builder
.Append ('\n');
692 writer
.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
693 writer
.Write (builder
);
694 writer
.WriteLine ("};\n\n");
696 builder
= new StringBuilder ();
698 for (int i
= 0; i
< count
; i
++) {
699 CultureInfoEntry ci
= cultures
[i
];
700 ci
.AppendTableRow (builder
);
703 builder
.Append (',');
704 builder
.Append ('\n');
707 writer
.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
708 writer
.Write (builder
);
709 writer
.WriteLine ("};\n\n");
711 cultures
.Sort (new ExportNameComparer ()); // Sort based on name
712 builder
= new StringBuilder ();
713 for (int i
= 0; i
< count
; i
++) {
714 CultureInfoEntry ci
= cultures
[i
];
715 var name
= ci
.GetExportName ().ToLowerInvariant ();
716 builder
.Append ("\t{" + Entry
.EncodeStringIdx (name
) + ", ");
717 builder
.Append (ci
.Row
+ "}");
719 builder
.Append (',');
721 builder
.AppendFormat ("\t /* {0} */", name
);
722 builder
.Append ('\n');
725 writer
.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
726 writer
.Write (builder
);
727 writer
.WriteLine ("};\n\n");
729 builder
= new StringBuilder ();
731 foreach (RegionInfoEntry r
in regions
) {
732 r
.AppendTableRow (builder
);
733 if (++rcount
!= regions
.Count
)
734 builder
.Append (',');
736 builder
.Append ('\n');
738 writer
.WriteLine ("static const RegionInfoEntry region_entries [] = {");
739 writer
.Write (builder
);
740 writer
.WriteLine ("};\n\n");
742 builder
= new StringBuilder ();
744 foreach (RegionInfoEntry ri
in regions
) {
745 builder
.Append ("\t{" + Entry
.EncodeStringIdx (ri
.TwoLetterISORegionName
) + ", ");
746 builder
.Append (ri
.Index
+ "}");
747 if (++rcount
!= regions
.Count
)
748 builder
.Append (',');
750 builder
.AppendFormat ("\t /* {0} */", ri
.TwoLetterISORegionName
);
751 builder
.Append ('\n');
754 writer
.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
755 writer
.Write (builder
);
756 writer
.WriteLine ("};\n\n");
758 writer
.WriteLine ("static const char locale_strings [] = {");
759 writer
.Write (Entry
.GetStrings ());
760 writer
.WriteLine ("};\n\n");
762 writer
.WriteLine ("#endif\n");
766 static void GetAllChildrenValues
<T
> (CultureInfoEntry entry
, List
<T
> values
, Func
<CultureInfoEntry
, T
> selector
)
768 foreach (var e
in entry
.Children
) {
772 values
.Add (selector (e
));
774 foreach (var e2
in e
.Children
) {
775 GetAllChildrenValues (e2
, values
, selector
);
780 static XmlDocument
GetXmlDocument (string path
)
782 var doc
= new XmlDocument ();
783 doc
.Load (new XmlTextReader (path
) { /*DtdProcessing = DtdProcessing.Ignore*/ }
);
787 bool Import (CultureInfoEntry data
, string locale
)
790 var sep
= locale
.Split ('_');
791 data
.Language
= sep
[0];
793 // CLDR strictly follow ISO names, .NET does not
794 // Replace names where non-iso2 is used, e.g. Norway
795 if (data
.Language
!= data
.TwoLetterISOLanguageName
) {
796 locale
= data
.TwoLetterISOLanguageName
;
797 if (sep
.Length
> 1) {
798 locale
+= string.Join ("_", sep
.Skip (1));
802 // Convert broken Chinese names to correct one
811 locale
= "zh_Hans_CN";
814 locale
= "zh_Hant_HK";
817 locale
= "zh_Hans_SG";
820 locale
= "zh_Hant_TW";
823 locale
= "zh_Hant_MO";
827 sep
= locale
.Split ('_');
829 string full_name
= Path
.Combine (data_root
, "main", locale
+ ".xml");
830 if (!File
.Exists (full_name
)) {
831 Console
.WriteLine ("Missing locale file for `{0}'", locale
);
833 // We could fill default values but that's not as simple as it seems. For instance for non-neutral
834 // cultures the next part could be territory or not.
837 XmlDocument doc
= null;
840 * Locale generation is done in several steps, first we
841 * read the root file which is the base invariant data
842 * then the supplemental root data,
843 * then the language file, the supplemental languages
844 * file then the locale file, then the supplemental
845 * locale file. Values in each descending file can
846 * overwrite previous values.
848 foreach (var part
in sep
) {
856 if (extra_parent_locales
.TryGetValue (fname
, out extra
)) {
857 xml
= GetXmlDocument (Path
.Combine (data_root
, "main", extra
+ ".xml"));
864 xml
= GetXmlDocument (Path
.Combine (data_root
, "main", fname
+ ".xml"));
872 // Extract localized locale name from language xml file. Have to do it after both language and territory are read
874 var el
= doc
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", data
.Language
));
876 data
.NativeName
= el
.InnerText
;
878 if (data
.Territory
!= null) {
879 el
= doc
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", data
.Territory
));
881 // TODO: Should read <localePattern>
882 data
.NativeName
= string.Format ("{0} ({1})", data
.NativeName
, el
.InnerText
);
883 data
.NativeTerritoryName
= el
.InnerText
;
887 // We have territory now we have to run the process again to extract currency symbol
888 if (region_currency
.TryGetValue (data
.Territory
, out currency
)) {
891 var xml
= GetXmlDocument (Path
.Combine (data_root
, "main", "root.xml"));
892 el
= xml
.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency
));
894 data
.NumberFormatEntry
.CurrencySymbol
= el
.InnerText
;
896 foreach (var part
in sep
) {
902 xml
= GetXmlDocument (Path
.Combine (data_root
, "main", fname
+ ".xml"));
903 el
= xml
.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency
));
905 data
.NumberFormatEntry
.CurrencySymbol
= el
.InnerText
;
907 el
= xml
.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", currency
));
909 data
.NativeCurrencyName
= el
.InnerText
;
915 // It looks like it never changes
916 data
.DateTimeFormatEntry
.TimeSeparator
= ":";
918 // TODO: Don't have input data available but most values are 2 with few exceptions for 1 and 3
919 // We don't add 3 as it's for some arabic states only
920 switch (data
.ThreeLetterISOLanguageName
) {
922 data
.NumberFormatEntry
.NumberDecimalDigits
=
923 data
.NumberFormatEntry
.PercentDecimalDigits
= 1;
926 data
.NumberFormatEntry
.NumberDecimalDigits
=
927 data
.NumberFormatEntry
.PercentDecimalDigits
= 2;
931 // TODO: For now we capture only native name for default calendar
932 data
.NativeCalendarNames
[((int) data
.CalendarType
& 0xFF) - 1] = data
.DateTimeFormatEntry
.NativeCalendarName
;
934 var lcdid_value
= int.Parse (data
.LCID
.Substring (2), NumberStyles
.HexNumber
);
935 Patterns
.FillValues (lcdid_value
, data
);
940 void Import (XmlDocument doc
, CultureInfoEntry ci
)
946 // Extract script & teritory
948 el
= doc
.SelectSingleNode ("ldml/identity/script");
950 ci
.Script
= el
.Attributes
["type"].Value
;
952 el
= doc
.SelectSingleNode ("ldml/identity/territory");
954 ci
.Territory
= el
.Attributes
["type"].Value
;
956 var df
= ci
.DateTimeFormatEntry
;
959 // Default calendar is for now always "gregorian"
961 case "th": case "th-TH":
962 calendar
= "buddhist";
963 ci
.CalendarType
= CalendarType
.ThaiBuddhist
; // typeof (ThaiBuddhistCalendar);
965 case "ar": case "ar-SA":
966 calendar
= "islamic";
967 ci
.CalendarType
= CalendarType
.UmAlQuraCalendar
; // typeof (UmAlQuraCalendar);
969 case "ps": case "ps-AF": case "prs": case "prs-AF": case "dv": case "dv-MV":
970 calendar
= "persian";
971 ci
.CalendarType
= CalendarType
.HijriCalendar
; // typeof (HijriCalendar);
974 calendar
= "gregorian";
975 ci
.CalendarType
= CalendarType
.Gregorian
; // typeof (GregorianCalendar);
976 ci
.GregorianCalendarType
= GregorianCalendarTypes
.Localized
;
980 var node
= doc
.SelectSingleNode (string.Format ("ldml/dates/calendars/calendar[@type='{0}']", calendar
));
982 el
= doc
.SelectSingleNode (string.Format ("ldml/localeDisplayNames/types/type[@type='{0}']", calendar
));
984 df
.NativeCalendarName
= el
.InnerText
;
987 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='wide']"/>
988 nodes
= node
.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
989 ProcessAllNodes (nodes
, df
.MonthNames
, AddOrReplaceValue
);
990 nodes
= node
.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='wide']/month");
991 ProcessAllNodes (nodes
, df
.MonthNames
, AddOrReplaceValue
);
993 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='abbreviated']"/>
994 nodes
= node
.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='abbreviated']/month");
995 ProcessAllNodes (nodes
, df
.AbbreviatedMonthNames
, AddOrReplaceValue
);
996 nodes
= node
.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='abbreviated']/month");
997 ProcessAllNodes (nodes
, df
.AbbreviatedMonthNames
, AddOrReplaceValue
);
999 nodes
= node
.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
1000 if (nodes
!= null) {
1001 ProcessAllNodes (nodes
, df
.MonthGenitiveNames
, AddOrReplaceValue
);
1004 // All values seem to match
1005 Array
.Copy (df
.AbbreviatedMonthNames
, df
.AbbreviatedMonthGenitiveNames
, df
.AbbreviatedMonthNames
.Length
);
1007 nodes
= node
.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='wide']/day");
1008 ProcessAllNodes (nodes
, df
.DayNames
, AddOrReplaceDayValue
);
1010 // Apply global rule first <alias source="locale" path="../../dayContext[@type='format']/dayWidth[@type='abbreviated']"/>
1011 nodes
= node
.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='abbreviated']/day");
1012 ProcessAllNodes (nodes
, df
.AbbreviatedDayNames
, AddOrReplaceDayValue
);
1013 nodes
= node
.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='abbreviated']/day");
1014 ProcessAllNodes (nodes
, df
.AbbreviatedDayNames
, AddOrReplaceDayValue
);
1016 // TODO: This is not really ShortestDayNames as .NET uses it
1017 // Apply global rules first <alias source="locale" path="../../dayContext[@type='stand-alone']/dayWidth[@type='narrow']"/>
1018 nodes
= node
.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='narrow']/day");
1019 ProcessAllNodes (nodes
, df
.ShortestDayNames
, AddOrReplaceDayValue
);
1020 nodes
= node
.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='narrow']/day");
1021 ProcessAllNodes (nodes
, df
.ShortestDayNames
, AddOrReplaceDayValue
);
1023 Cannot really be used it's too different to .NET and most app rely on it
1025 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='full']/dateFormat/pattern");
1027 df.LongDatePattern = ConvertDatePatternFormat (el.InnerText);
1029 // Medium is our short
1030 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='medium']/dateFormat/pattern");
1032 df.ShortDatePattern = ConvertDatePatternFormat (el.InnerText);
1034 // Medium is our Long
1035 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='medium']/timeFormat/pattern");
1037 df.LongTimePattern = ConvertTimePatternFormat (el.InnerText);
1039 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='short']/timeFormat/pattern");
1041 df.ShortTimePattern = ConvertTimePatternFormat (el.InnerText);
1043 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='yyyyMMMM']");
1045 df.YearMonthPattern = ConvertDatePatternFormat (el.InnerText);
1047 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='MMMMdd']");
1049 df.MonthDayPattern = ConvertDatePatternFormat (el.InnerText);
1051 el
= node
.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='am']");
1053 // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1054 el
= node
.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='am']");
1057 df
.AMDesignator
= el
.InnerText
;
1059 el
= node
.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='pm']");
1061 // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1062 el
= node
.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='pm']");
1066 df
.PMDesignator
= el
.InnerText
;
1069 var ni
= ci
.NumberFormatEntry
;
1071 node
= doc
.SelectSingleNode ("ldml/numbers/symbols");
1073 el
= node
.SelectSingleNode ("decimal");
1075 ni
.NumberDecimalSeparator
=
1076 ni
.PercentDecimalSeparator
=
1077 ni
.CurrencyDecimalSeparator
= el
.InnerText
;
1080 el
= node
.SelectSingleNode ("plusSign");
1082 ni
.PositiveSign
= el
.InnerText
;
1084 el
= node
.SelectSingleNode ("minusSign");
1086 ni
.NegativeSign
= el
.InnerText
;
1088 el
= node
.SelectSingleNode ("infinity");
1090 // We cannot use the value from CLDR because many broken
1091 // .NET serializers (e.g. JSON) use text value of NegativeInfinity
1092 // and different value would break interoperability with .NET
1093 var inf
= GetInfinitySymbol (ci
);
1095 ni
.InfinitySymbol
= inf
;
1096 else if (el
!= null && el
.InnerText
!= "∞") {
1097 ni
.InfinitySymbol
= el
.InnerText
;
1100 el
= node
.SelectSingleNode ("perMille");
1102 ni
.PerMilleSymbol
= el
.InnerText
;
1104 el
= node
.SelectSingleNode ("nan");
1106 ni
.NaNSymbol
= el
.InnerText
;
1108 el
= node
.SelectSingleNode ("percentSign");
1110 ni
.PercentSymbol
= el
.InnerText
;
1112 el
= node
.SelectSingleNode ("group");
1114 ni
.NumberGroupSeparator
=
1115 ni
.PercentGroupSeparator
=
1116 ni
.CurrencyGroupSeparator
= el
.InnerText
;
1121 string GetInfinitySymbol (CultureInfoEntry ci
)
1124 switch (ci
.TwoLetterISOLanguageName
) {
1129 return "+nekonečno";
1131 return "+unendlich";
1146 return "+nieskończoność";
1149 return "бесконечность";
1151 return "neskončnost";
1165 static string ConvertDatePatternFormat (string format
)
1168 // LDMR uses different characters for some fields
1169 // http://unicode.org/reports/tr35/#Date_Format_Patterns
1171 format
= format
.Replace ("EEEE", "dddd"); // The full name of the day of the week
1172 format
= format
.Replace ("LLLL", "MMMM"); // The full month name
1174 if (format
.EndsWith (" y", StringComparison
.Ordinal
))
1180 static string ConvertTimePatternFormat (string format
)
1182 format
= format
.Replace ("a", "tt"); // AM or PM
1186 static void ProcessAllNodes (XmlNodeList list
, IList
<string> values
, Action
<IList
<string>, string, string> convertor
)
1188 foreach (XmlNode entry
in list
) {
1189 var index
= entry
.Attributes
["type"].Value
;
1190 var value = entry
.InnerText
;
1191 convertor (values
, index
, value);
1195 // All text indexes are 1-based
1196 static void AddOrReplaceValue (IList
<string> list
, string oneBasedIndex
, string value)
1198 int index
= int.Parse (oneBasedIndex
);
1199 AddOrReplaceValue (list
, index
- 1, value);
1202 static readonly string[] day_types
= new string[] { "sun", "mon", "tue", "wed", "thu", "fri", "sat" }
;
1204 static void AddOrReplaceDayValue (IList
<string> list
, string dayType
, string value)
1206 int index
= Array
.IndexOf (day_types
, dayType
);
1207 AddOrReplaceValue (list
, index
, value);
1210 static void AddOrReplaceValue (IList
<string> list
, int index
, string value)
1212 if (list
.Count
<= index
)
1213 ((List
<string>) list
).AddRange (new string[index
- list
.Count
+ 1]);
1215 list
[index
] = value;
1218 sealed class LcidComparer
: IComparer
<CultureInfoEntry
>
1220 public int Compare (CultureInfoEntry x
, CultureInfoEntry y
)
1222 return x
.LCID
.CompareTo (y
.LCID
);
1226 sealed class ExportNameComparer
: IComparer
<CultureInfoEntry
>
1228 public int Compare (CultureInfoEntry x
, CultureInfoEntry y
)
1230 return String
.Compare (x
.GetExportName (), y
.GetExportName (), StringComparison
.OrdinalIgnoreCase
);
1234 class RegionComparer
: IComparer
<RegionInfoEntry
>
1236 public int Compare (RegionInfoEntry x
, RegionInfoEntry y
)
1238 return x
.TwoLetterISORegionName
.CompareTo (y
.TwoLetterISORegionName
);