[monodoc] Clarified message when a backing file isn't found
[mono-project.git] / tools / locale-builder / Driver.cs
blob1a44d5eb7e19ef4a4ba5fe1ef74cd343dc0595a0
1 //
2 // Driver.cs
3 //
4 // Authors:
5 // Jackson Harper (jackson@ximian.com)
6 // Atsushi Enomoto (atsushi@ximian.com)
7 // Marek Safar <marek.safar@gmail.com>
8 //
9 // (C) 2004-2005 Novell, Inc (http://www.novell.com)
10 // Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System;
33 using System.IO;
34 using System.Text;
35 using System.Xml;
36 using System.Globalization;
37 using System.Text.RegularExpressions;
38 using System.Collections.Generic;
39 using System.Linq;
41 namespace Mono.Tools.LocaleBuilder
43 public class Driver
45 static readonly string data_root = Path.Combine ("CLDR", "common");
47 public static void Main (string[] args)
49 Driver d = new Driver ();
50 ParseArgs (args, d);
51 d.Run ();
54 private static void ParseArgs (string[] args, Driver d)
56 for (int i = 0; i < args.Length; i++) {
57 if (args[i] == "--lang" && i + 1 < args.Length)
58 d.Lang = args[++i];
59 else if (args[i] == "--locales" && i + 1 < args.Length)
60 d.Locales = args[++i];
61 else if (args[i] == "--header" && i + 1 < args.Length)
62 d.HeaderFileName = args[++i];
63 else if (args[i] == "--compare")
64 d.OutputCompare = true;
68 private string lang;
69 private string locales;
70 private string header_name;
71 List<CultureInfoEntry> cultures;
72 Dictionary<string, string> region_currency;
73 Dictionary<string, string> currency_fractions;
74 Dictionary<string, string> extra_parent_locales;
76 // The lang is the language that display names will be displayed in
77 public string Lang
79 get
81 if (lang == null)
82 lang = "en";
83 return lang;
85 set { lang = value; }
88 public string Locales
90 get { return locales; }
91 set { locales = value; }
94 public string HeaderFileName
96 get
98 if (header_name == null)
99 return "culture-info-tables.h";
100 return header_name;
102 set { header_name = value; }
105 public bool OutputCompare { get; set; }
107 void Print ()
109 cultures.Sort ((a, b) => int.Parse (a.LCID.Substring (2), NumberStyles.HexNumber).CompareTo (int.Parse (b.LCID.Substring (2), NumberStyles.HexNumber)));
111 var writer = Console.Out;
113 foreach (var c in cultures) {
114 writer.WriteLine ("Name: {0}, LCID {1}", c.OriginalName, c.LCID);
116 writer.WriteLine ("{0}: {1}", "DisplayName", c.DisplayName);
117 writer.WriteLine ("{0}: {1}", "EnglishName", c.EnglishName);
118 writer.WriteLine ("{0}: {1}", "NativeName", c.NativeName);
119 // writer.WriteLine ("{0}: {1}", "OptionalCalendars", c.OptionalCalendars);
120 writer.WriteLine ("{0}: {1}", "ThreeLetterISOLanguageName", c.ThreeLetterISOLanguageName);
121 writer.WriteLine ("{0}: {1}", "ThreeLetterWindowsLanguageName", c.ThreeLetterWindowsLanguageName);
122 writer.WriteLine ("{0}: {1}", "TwoLetterISOLanguageName", c.TwoLetterISOLanguageName);
123 writer.WriteLine ("{0}: {1}", "Calendar", GetCalendarType (c.CalendarType));
125 var df = c.DateTimeFormatEntry;
126 writer.WriteLine ("-- DateTimeFormat --");
127 Dump (writer, df.AbbreviatedDayNames, "AbbreviatedDayNames");
128 Dump (writer, df.AbbreviatedMonthGenitiveNames, "AbbreviatedMonthGenitiveNames");
129 Dump (writer, df.AbbreviatedMonthNames, "AbbreviatedMonthNames");
130 writer.WriteLine ("{0}: {1}", "AMDesignator", df.AMDesignator);
131 writer.WriteLine ("{0}: {1}", "CalendarWeekRule", (CalendarWeekRule) df.CalendarWeekRule);
132 writer.WriteLine ("{0}: {1}", "DateSeparator", df.DateSeparator);
133 Dump (writer, df.DayNames, "DayNames");
134 writer.WriteLine ("{0}: {1}", "FirstDayOfWeek", (DayOfWeek) df.FirstDayOfWeek);
135 // Dump (writer, df.GetAllDateTimePatterns (), "GetAllDateTimePatterns");
136 writer.WriteLine ("{0}: {1}", "LongDatePattern", df.LongDatePattern);
137 writer.WriteLine ("{0}: {1}", "LongTimePattern", df.LongTimePattern);
138 writer.WriteLine ("{0}: {1}", "MonthDayPattern", df.MonthDayPattern);
139 Dump (writer, df.MonthGenitiveNames, "MonthGenitiveNames");
140 Dump (writer, df.MonthNames, "MonthNames");
141 writer.WriteLine ("{0}: {1}", "NativeCalendarName", df.NativeCalendarName);
142 writer.WriteLine ("{0}: {1}", "PMDesignator", df.PMDesignator);
143 writer.WriteLine ("{0}: {1}", "ShortDatePattern", df.ShortDatePattern);
144 Dump (writer, df.ShortestDayNames, "ShortestDayNames");
145 writer.WriteLine ("{0}: {1}", "ShortTimePattern", df.ShortTimePattern);
146 writer.WriteLine ("{0}: {1}", "TimeSeparator", df.TimeSeparator);
147 writer.WriteLine ("{0}: {1}", "YearMonthPattern", df.YearMonthPattern);
149 var ti = c.TextInfoEntry;
150 writer.WriteLine ("-- TextInfo --");
151 writer.WriteLine ("{0}: {1}", "ANSICodePage", ti.ANSICodePage);
152 writer.WriteLine ("{0}: {1}", "EBCDICCodePage", ti.EBCDICCodePage);
153 writer.WriteLine ("{0}: {1}", "IsRightToLeft", ti.IsRightToLeft);
154 writer.WriteLine ("{0}: {1}", "ListSeparator", ti.ListSeparator);
155 writer.WriteLine ("{0}: {1}", "MacCodePage", ti.MacCodePage);
156 writer.WriteLine ("{0}: {1}", "OEMCodePage", ti.OEMCodePage);
158 var nf = c.NumberFormatEntry;
159 writer.WriteLine ("-- NumberFormat --");
160 writer.WriteLine ("{0}: {1}", "CurrencyDecimalDigits", nf.CurrencyDecimalDigits);
161 writer.WriteLine ("{0}: {1}", "CurrencyDecimalSeparator", nf.CurrencyDecimalSeparator);
162 writer.WriteLine ("{0}: {1}", "CurrencyGroupSeparator", nf.CurrencyGroupSeparator);
163 Dump (writer, nf.CurrencyGroupSizes, "CurrencyGroupSizes", true);
164 writer.WriteLine ("{0}: {1}", "CurrencyNegativePattern", nf.CurrencyNegativePattern);
165 writer.WriteLine ("{0}: {1}", "CurrencyPositivePattern", nf.CurrencyPositivePattern);
166 writer.WriteLine ("{0}: {1}", "CurrencySymbol", nf.CurrencySymbol);
167 writer.WriteLine ("{0}: {1}", "DigitSubstitution", nf.DigitSubstitution);
168 writer.WriteLine ("{0}: {1}", "NaNSymbol", nf.NaNSymbol);
169 Dump (writer, nf.NativeDigits, "NativeDigits");
170 writer.WriteLine ("{0}: {1}", "NegativeInfinitySymbol", nf.NegativeInfinitySymbol);
171 writer.WriteLine ("{0}: {1}", "NegativeSign", nf.NegativeSign);
172 writer.WriteLine ("{0}: {1}", "NumberDecimalDigits", nf.NumberDecimalDigits);
173 writer.WriteLine ("{0}: {1}", "NumberDecimalSeparator", nf.NumberDecimalSeparator);
174 writer.WriteLine ("{0}: {1}", "NumberGroupSeparator", nf.NumberGroupSeparator);
175 Dump (writer, nf.NumberGroupSizes, "NumberGroupSizes", true);
176 writer.WriteLine ("{0}: {1}", "NumberNegativePattern", nf.NumberNegativePattern);
177 writer.WriteLine ("{0}: {1}", "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);
206 writer.WriteLine ();
210 static Type GetCalendarType (CalendarType ct)
212 switch (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);
221 default:
222 throw new NotImplementedException ();
226 static void Dump<T> (TextWriter tw, IList<T> values, string name, bool stopOnNull = false) where T : class
228 tw.Write (name);
229 tw.Write (": ");
231 for (int i = 0; i < values.Count; ++i) {
232 var v = values[i];
234 if (stopOnNull && v == null)
235 break;
237 if (i > 0)
238 tw.Write (", ");
240 tw.Write (v);
243 tw.WriteLine ();
246 void Run ()
248 Regex locales_regex = null;
249 if (Locales != 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);
265 // Parent locales
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")
271 continue;
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))
283 continue;
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))
296 continue;
298 cultures.Add (ci);
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));
308 if (el != null)
309 ci.EnglishName = el.InnerText;
311 string s = null;
312 if (ci.Script != null) {
313 el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/scripts/script[@type='{0}']", ci.Script));
314 if (el != null)
315 s = el.InnerText;
318 if (ci.Territory != null) {
319 el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci.Territory));
320 if (el != null) {
321 if (s == null)
322 s = el.InnerText;
323 else
324 s = string.Join (", ", s, el.InnerText);
328 switch (ci.ThreeLetterWindowsLanguageName) {
329 case "CHT":
330 s = "Traditional";
331 break;
332 case "CHS":
333 s = "Simplified";
334 break;
337 if (s != null)
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)) {
353 ci.Children.Add (p);
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")) {
365 if (entry.Attributes ["alt"] != null)
366 continue;
368 DayOfWeek dow;
369 switch (entry.Attributes["day"].Value) {
370 case "mon":
371 dow = DayOfWeek.Monday;
372 break;
373 case "fri":
374 dow = DayOfWeek.Friday;
375 break;
376 case "sat":
377 dow = DayOfWeek.Saturday;
378 break;
379 case "sun":
380 dow = DayOfWeek.Sunday;
381 break;
382 default:
383 throw new NotImplementedException ();
386 var territories = entry.Attributes["territories"].Value.Split ();
387 foreach (var t in territories) {
388 territory2dayofweek.Add (t, dow);
392 var territory2wr = new Dictionary<string, CalendarWeekRule> (StringComparer.OrdinalIgnoreCase);
393 foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/minDays")) {
394 CalendarWeekRule rule;
396 switch (entry.Attributes["count"].InnerText) {
397 case "1":
398 rule = CalendarWeekRule.FirstDay;
399 break;
400 case "4":
401 rule = CalendarWeekRule.FirstFourDayWeek;
402 break;
403 default:
404 throw new NotImplementedException ();
407 var territories = entry.Attributes["territories"].InnerText.Split ();
408 foreach (var t in territories)
409 territory2wr[t] = rule;
413 // Fill all territory speficic data where territory is available
415 var non_metric = new HashSet<string> ();
416 foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/measurementData/measurementSystem[@type='US']")) {
417 var territories = entry.Attributes["territories"].InnerText.Split ();
418 foreach (var t in territories)
419 non_metric.Add (t);
422 foreach (var ci in cultures) {
423 if (ci.Territory == null)
424 continue;
426 DayOfWeek value;
427 if (territory2dayofweek.TryGetValue (ci.Territory, out value)) {
428 ci.DateTimeFormatEntry.FirstDayOfWeek = (int) value;
431 CalendarWeekRule rule;
432 if (territory2wr.TryGetValue (ci.Territory, out rule)) {
433 ci.DateTimeFormatEntry.CalendarWeekRule = (int) rule;
436 RegionInfoEntry region = regions.Where (l => l.Name == ci.Territory).FirstOrDefault ();
437 if (region == null) {
438 region = new RegionInfoEntry () {
439 CurrencySymbol = ci.NumberFormatEntry.CurrencySymbol,
440 EnglishName = ci.EnglishName,
441 NativeName = ci.NativeTerritoryName,
442 Name = ci.Territory,
443 TwoLetterISORegionName = ci.Territory,
444 CurrencyNativeName = ci.NativeCurrencyName
447 var tc = supplemental.SelectSingleNode (string.Format ("supplementalData/codeMappings/territoryCodes[@type='{0}']", ci.Territory));
448 region.ThreeLetterISORegionName = tc.Attributes["alpha3"].Value;
449 region.ThreeLetterWindowsRegionName = region.ThreeLetterISORegionName;
451 var el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci.Territory));
452 region.EnglishName = el.InnerText;
453 region.DisplayName = region.EnglishName;
455 region.ISOCurrencySymbol = region_currency[ci.Territory];
457 el = doc_english.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", region.ISOCurrencySymbol));
458 region.CurrencyEnglishName = el.InnerText;
460 if (non_metric.Contains (ci.Territory))
461 region.IsMetric = false;
463 var lcdid_value = int.Parse (ci.LCID.Substring (2), NumberStyles.HexNumber);
464 Patterns.FillValues (lcdid_value, region);
465 regions.Add (region);
468 string fraction_value;
469 if (currency_fractions.TryGetValue (region.ISOCurrencySymbol, out fraction_value)) {
470 ci.NumberFormatEntry.CurrencyDecimalDigits = fraction_value;
473 ci.RegionInfoEntry = region;
477 // Fill neutral cultures territory data
479 foreach (var ci in cultures) {
480 var dtf = ci.DateTimeFormatEntry;
481 if (dtf.FirstDayOfWeek == null) {
482 switch (ci.Name) {
483 case "ar":
484 dtf.FirstDayOfWeek = (int) DayOfWeek.Saturday;
485 break;
486 case "en":
487 case "pt":
488 case "zh-Hans":
489 dtf.FirstDayOfWeek = (int) DayOfWeek.Sunday;
490 break;
491 case "es":
492 case "fr":
493 case "bn":
494 case "sr-Cyrl":
495 case "sr-Latn":
496 dtf.FirstDayOfWeek = (int) DayOfWeek.Monday;
497 break;
498 default:
499 List<int?> all_fdow = new List<int?> ();
500 GetAllChildrenValues (ci, all_fdow, l => l.DateTimeFormatEntry.FirstDayOfWeek);
501 var children = all_fdow.Where (l => l != null).Distinct ().ToList ();
503 if (children.Count == 1) {
504 dtf.FirstDayOfWeek = children[0];
505 } else if (children.Count == 0) {
506 if (!ci.HasMissingLocale)
507 Console.WriteLine ("No week data for `{0}'", ci.Name);
509 // Default to Sunday
510 dtf.FirstDayOfWeek = (int) DayOfWeek.Sunday;
511 } else {
512 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
513 // We have to manually disambiguate the correct entry (which is artofficial anyway)
514 throw new ApplicationException (string.Format ("Ambiguous week data for `{0}'", ci.Name));
517 break;
521 if (dtf.CalendarWeekRule == null) {
522 switch (ci.Name) {
523 case "ar":
524 case "en":
525 case "es":
526 case "zh-Hans":
527 case "pt":
528 case "fr":
529 case "bn":
530 dtf.CalendarWeekRule = (int) CalendarWeekRule.FirstDay;
531 break;
532 default:
533 List<int?> all_cwr = new List<int?> ();
534 GetAllChildrenValues (ci, all_cwr, l => l.DateTimeFormatEntry.CalendarWeekRule);
535 var children = all_cwr.Where (l => l != null).Distinct ().ToList ();
537 if (children.Count == 1) {
538 dtf.CalendarWeekRule = children[0];
539 } else if (children.Count == 0) {
540 if (!ci.HasMissingLocale)
541 Console.WriteLine ("No calendar week data for `{0}'", ci.Name);
544 // Default to FirstDay
545 dtf.CalendarWeekRule = (int) CalendarWeekRule.FirstDay;
546 } else {
547 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
548 // We have to manually disambiguate the correct entry (which is artofficial anyway)
549 throw new ApplicationException (string.Format ("Ambiguous calendar data for `{0}'", ci.Name));
552 break;
556 var nfe = ci.NumberFormatEntry;
557 if (nfe.CurrencySymbol == null) {
558 switch (ci.Name) {
559 case "ar":
560 nfe.CurrencySymbol = "ر.س.‏";
561 break;
562 case "en":
563 nfe.CurrencySymbol = "$";
564 break;
565 case "bs":
566 nfe.CurrencySymbol = "KM";
567 break;
568 case "es":
569 case "fr":
570 case "de":
571 case "it":
572 case "se":
573 nfe.CurrencySymbol = "€";
574 break;
575 case "hr":
576 nfe.CurrencySymbol = "kn";
577 break;
578 case "pt":
579 nfe.CurrencySymbol = "R$";
580 break;
581 case "sv":
582 nfe.CurrencySymbol = "kr";
583 break;
584 case "ms":
585 nfe.CurrencySymbol = "RM";
586 break;
587 case "bn":
588 nfe.CurrencySymbol = "টা";
589 break;
590 case "sr-Cyrl":
591 nfe.CurrencySymbol = "Дин.";
592 break;
593 case "sr-Latn":
594 case "sr":
595 nfe.CurrencySymbol = "Din.";
596 break;
597 case "zh":
598 case "zh-Hans":
599 nfe.CurrencySymbol = "¥";
600 break;
601 case "zh-Hant":
602 nfe.CurrencySymbol = "HK$";
603 break;
605 default:
606 var all_currencies = new List<string> ();
607 GetAllChildrenValues (ci, all_currencies, l => l.NumberFormatEntry.CurrencySymbol);
608 var children = all_currencies.Where (l => l != null).Distinct ().ToList ();
610 if (children.Count == 1) {
611 nfe.CurrencySymbol = children[0];
612 } else if (children.Count == 0) {
613 if (!ci.HasMissingLocale)
614 Console.WriteLine ("No currency data for `{0}'", ci.Name);
617 } else {
618 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
619 // We have to manually disambiguate the correct entry (which is artofficial anyway)
620 throw new ApplicationException (string.Format ("Ambiguous currency data for `{0}'. Possible values '{1}'", ci.Name, string.Join (", ", children)));
623 break;
627 if (nfe.CurrencyDecimalDigits == null) {
628 var all_digits = new List<string> ();
629 GetAllChildrenValues (ci, all_digits, l => l.NumberFormatEntry.CurrencyDecimalDigits);
630 var children = all_digits.Where (l => l != null).Distinct ().ToList ();
632 if (children.Count == 1) {
633 nfe.CurrencyDecimalDigits = children[0];
634 } else if (children.Count == 0) {
635 if (!ci.HasMissingLocale)
636 Console.WriteLine ("No currency decimal digits data for `{0}'", ci.Name);
638 nfe.CurrencyDecimalDigits = "2";
639 } else if (ci.IsNeutral) {
640 nfe.CurrencyDecimalDigits = "2";
641 } else {
642 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
643 // We have to manually disambiguate the correct entry (which is artofficial anyway)
644 throw new ApplicationException (string.Format ("Ambiguous currency decimal digits data for `{0}'", ci.Name));
649 if (OutputCompare)
650 Print ();
652 regions.Sort (new RegionComparer ());
653 for (int i = 0; i < regions.Count; ++i)
654 regions[i].Index = i;
657 * Dump each table individually. Using StringBuilders
658 * because it is easier to debug, should switch to just
659 * writing to streams eventually.
661 using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
662 writer.NewLine = "\n";
663 writer.WriteLine ();
664 writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
665 writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
666 writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
667 writer.WriteLine ("\n");
669 writer.WriteLine ("#define NUM_CULTURE_ENTRIES {0}", cultures.Count);
670 writer.WriteLine ("#define NUM_REGION_ENTRIES {0}", regions.Count);
672 writer.WriteLine ("\n");
674 // Sort the cultures by lcid
675 cultures.Sort (new LcidComparer ());
677 StringBuilder builder = new StringBuilder ();
678 int row = 0;
679 int count = cultures.Count;
680 for (int i = 0; i < count; i++) {
681 CultureInfoEntry ci = cultures[i];
682 if (ci.DateTimeFormatEntry == null)
683 continue;
684 ci.DateTimeFormatEntry.AppendTableRow (builder);
685 ci.DateTimeFormatEntry.Row = row++;
686 if (i + 1 < count)
687 builder.Append (',');
688 builder.Append ('\n');
691 writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
692 writer.Write (builder);
693 writer.WriteLine ("};\n\n");
695 builder = new StringBuilder ();
696 row = 0;
697 for (int i = 0; i < count; i++) {
698 CultureInfoEntry ci = cultures[i];
699 if (ci.NumberFormatEntry == null)
700 continue;
701 ci.NumberFormatEntry.AppendTableRow (builder);
702 ci.NumberFormatEntry.Row = row++;
703 if (i + 1 < count)
704 builder.Append (',');
705 builder.Append ('\n');
708 writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
709 writer.Write (builder);
710 writer.WriteLine ("};\n\n");
712 builder = new StringBuilder ();
713 row = 0;
714 for (int i = 0; i < count; i++) {
715 CultureInfoEntry ci = cultures[i];
716 ci.AppendTableRow (builder);
717 ci.Row = row++;
718 if (i + 1 < count)
719 builder.Append (',');
720 builder.Append ('\n');
723 writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
724 writer.Write (builder);
725 writer.WriteLine ("};\n\n");
727 cultures.Sort (new ExportNameComparer ()); // Sort based on name
728 builder = new StringBuilder ();
729 for (int i = 0; i < count; i++) {
730 CultureInfoEntry ci = cultures[i];
731 var name = ci.GetExportName ().ToLowerInvariant ();
732 builder.Append ("\t{" + Entry.EncodeStringIdx (name) + ", ");
733 builder.Append (ci.Row + "}");
734 if (i + 1 < count)
735 builder.Append (',');
737 builder.AppendFormat ("\t /* {0} */", name);
738 builder.Append ('\n');
741 writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
742 writer.Write (builder);
743 writer.WriteLine ("};\n\n");
745 builder = new StringBuilder ();
746 int rcount = 0;
747 foreach (RegionInfoEntry r in regions) {
748 r.AppendTableRow (builder);
749 if (++rcount != regions.Count)
750 builder.Append (',');
752 builder.Append ('\n');
754 writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
755 writer.Write (builder);
756 writer.WriteLine ("};\n\n");
758 builder = new StringBuilder ();
759 rcount = 0;
760 foreach (RegionInfoEntry ri in regions) {
761 builder.Append ("\t{" + Entry.EncodeStringIdx (ri.TwoLetterISORegionName) + ", ");
762 builder.Append (ri.Index + "}");
763 if (++rcount != regions.Count)
764 builder.Append (',');
766 builder.AppendFormat ("\t /* {0} */", ri.TwoLetterISORegionName);
767 builder.Append ('\n');
770 writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
771 writer.Write (builder);
772 writer.WriteLine ("};\n\n");
774 writer.WriteLine ("static const char locale_strings [] = {");
775 writer.Write (Entry.GetStrings ());
776 writer.WriteLine ("};\n\n");
778 writer.WriteLine ("#endif\n");
782 static void GetAllChildrenValues<T> (CultureInfoEntry entry, List<T> values, Func<CultureInfoEntry, T> selector)
784 foreach (var e in entry.Children) {
785 if (e == entry)
786 continue;
788 values.Add (selector (e));
790 foreach (var e2 in e.Children) {
791 GetAllChildrenValues (e2, values, selector);
796 static XmlDocument GetXmlDocument (string path)
798 var doc = new XmlDocument ();
799 doc.Load (new XmlTextReader (path) { /*DtdProcessing = DtdProcessing.Ignore*/ } );
800 return doc;
803 bool Import (CultureInfoEntry data, string locale)
805 string fname = null;
806 var sep = locale.Split ('_');
807 data.Language = sep[0];
809 // CLDR strictly follow ISO names, .NET does not
810 // Replace names where non-iso2 is used, e.g. Norway
811 if (data.Language != data.TwoLetterISOLanguageName) {
812 locale = data.TwoLetterISOLanguageName;
813 if (sep.Length > 1) {
814 locale += string.Join ("_", sep.Skip (1));
818 // Convert broken Chinese names to correct one
819 switch (locale) {
820 case "zh_CHS":
821 locale = "zh_Hans";
822 break;
823 case "zh_CHT":
824 locale = "zh_Hant";
825 break;
826 case "zh_CN":
827 locale = "zh_Hans_CN";
828 break;
829 case "zh_HK":
830 locale = "zh_Hant_HK";
831 break;
832 case "zh_SG":
833 locale = "zh_Hans_SG";
834 break;
835 case "zh_TW":
836 locale = "zh_Hant_TW";
837 break;
838 case "zh_MO":
839 locale = "zh_Hant_MO";
840 break;
843 sep = locale.Split ('_');
845 string full_name = Path.Combine (data_root, "main", locale + ".xml");
846 if (!File.Exists (full_name)) {
847 Console.WriteLine ("Missing locale file for `{0}'", locale);
849 // We could fill default values but that's not as simple as it seems. For instance for non-neutral
850 // cultures the next part could be territory or not.
851 return false;
852 } else {
853 XmlDocument doc = null;
856 * Locale generation is done in several steps, first we
857 * read the root file which is the base invariant data
858 * then the supplemental root data,
859 * then the language file, the supplemental languages
860 * file then the locale file, then the supplemental
861 * locale file. Values in each descending file can
862 * overwrite previous values.
864 foreach (var part in sep) {
865 if (fname != null)
866 fname += "_";
868 fname += part;
870 XmlDocument xml;
871 string extra;
872 if (extra_parent_locales.TryGetValue (fname, out extra)) {
873 xml = GetXmlDocument (Path.Combine (data_root, "main", extra + ".xml"));
874 if (doc == null)
875 doc = xml;
877 Import (xml, data);
880 xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
881 if (doc == null)
882 doc = xml;
884 Import (xml, data);
888 // Extract localized locale name from language xml file. Have to do it after both language and territory are read
890 var el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", data.Language));
891 if (el != null)
892 data.NativeName = el.InnerText;
894 if (data.Territory != null) {
895 el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", data.Territory));
896 if (el != null) {
897 // TODO: Should read <localePattern>
898 data.NativeName = string.Format ("{0} ({1})", data.NativeName, el.InnerText);
899 data.NativeTerritoryName = el.InnerText;
902 string currency;
903 // We have territory now we have to run the process again to extract currency symbol
904 if (region_currency.TryGetValue (data.Territory, out currency)) {
905 fname = null;
907 var xml = GetXmlDocument (Path.Combine (data_root, "main", "root.xml"));
908 el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
909 if (el != null)
910 data.NumberFormatEntry.CurrencySymbol = el.InnerText;
912 foreach (var part in sep) {
913 if (fname != null)
914 fname += "_";
916 fname += part;
918 xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
919 el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
920 if (el != null)
921 data.NumberFormatEntry.CurrencySymbol = el.InnerText;
923 el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", currency));
924 if (el != null)
925 data.NativeCurrencyName = el.InnerText;
931 // It looks like it never changes
932 data.DateTimeFormatEntry.TimeSeparator = ":";
934 // TODO: Don't have input data available but most values are 2 with few exceptions for 1 and 3
935 // We don't add 3 as it's for some arabic states only
936 switch (data.ThreeLetterISOLanguageName) {
937 case "amh":
938 data.NumberFormatEntry.NumberDecimalDigits =
939 data.NumberFormatEntry.PercentDecimalDigits = 1;
940 break;
941 default:
942 data.NumberFormatEntry.NumberDecimalDigits =
943 data.NumberFormatEntry.PercentDecimalDigits = 2;
944 break;
947 // TODO: For now we capture only native name for default calendar
948 data.NativeCalendarNames[((int) data.CalendarType & 0xFF) - 1] = data.DateTimeFormatEntry.NativeCalendarName;
950 var lcdid_value = int.Parse (data.LCID.Substring (2), NumberStyles.HexNumber);
951 Patterns.FillValues (lcdid_value, data);
953 return true;
956 void Import (XmlDocument doc, CultureInfoEntry ci)
958 XmlNodeList nodes;
959 XmlNode el;
962 // Extract script & teritory
964 el = doc.SelectSingleNode ("ldml/identity/script");
965 if (el != null)
966 ci.Script = el.Attributes["type"].Value;
968 el = doc.SelectSingleNode ("ldml/identity/territory");
969 if (el != null)
970 ci.Territory = el.Attributes["type"].Value;
972 var df = ci.DateTimeFormatEntry;
974 string calendar;
975 // Default calendar is for now always "gregorian"
976 switch (ci.Name) {
977 case "th": case "th-TH":
978 calendar = "buddhist";
979 ci.CalendarType = CalendarType.ThaiBuddhist; // typeof (ThaiBuddhistCalendar);
980 break;
981 case "ar": case "ar-SA":
982 calendar = "islamic";
983 ci.CalendarType = CalendarType.UmAlQuraCalendar; // typeof (UmAlQuraCalendar);
984 break;
985 case "ps": case "ps-AF": case "prs": case "prs-AF": case "dv": case "dv-MV":
986 calendar = "persian";
987 ci.CalendarType = CalendarType.HijriCalendar; // typeof (HijriCalendar);
988 break;
989 default:
990 calendar = "gregorian";
991 ci.CalendarType = CalendarType.Gregorian; // typeof (GregorianCalendar);
992 ci.GregorianCalendarType = GregorianCalendarTypes.Localized;
993 break;
996 var node = doc.SelectSingleNode (string.Format ("ldml/dates/calendars/calendar[@type='{0}']", calendar));
997 if (node != null) {
998 el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/types/type[@type='{0}']", calendar));
999 if (el != null)
1000 df.NativeCalendarName = el.InnerText;
1003 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='wide']"/>
1004 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
1005 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
1006 nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='wide']/month");
1007 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
1009 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='abbreviated']"/>
1010 if (ci.Name == "ja" || ci.Name == "ja-JP") {
1011 // Use common number style
1012 } else {
1013 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='abbreviated']/month");
1014 ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
1015 nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='abbreviated']/month");
1016 ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
1019 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
1020 if (nodes != null) {
1021 ProcessAllNodes (nodes, df.MonthGenitiveNames, AddOrReplaceValue);
1024 // All values seem to match
1025 Array.Copy (df.AbbreviatedMonthNames, df.AbbreviatedMonthGenitiveNames, df.AbbreviatedMonthNames.Length);
1027 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='wide']/day");
1028 ProcessAllNodes (nodes, df.DayNames, AddOrReplaceDayValue);
1030 // Apply global rule first <alias source="locale" path="../../dayContext[@type='format']/dayWidth[@type='abbreviated']"/>
1031 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='abbreviated']/day");
1032 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
1033 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='abbreviated']/day");
1034 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
1036 // TODO: This is not really ShortestDayNames as .NET uses it
1037 // Apply global rules first <alias source="locale" path="../../dayContext[@type='stand-alone']/dayWidth[@type='narrow']"/>
1038 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='narrow']/day");
1039 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1040 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='narrow']/day");
1041 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1043 Cannot really be used it's too different to .NET and most app rely on it
1045 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='full']/dateFormat/pattern");
1046 if (el != null)
1047 df.LongDatePattern = ConvertDatePatternFormat (el.InnerText);
1049 // Medium is our short
1050 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='medium']/dateFormat/pattern");
1051 if (el != null)
1052 df.ShortDatePattern = ConvertDatePatternFormat (el.InnerText);
1054 // Medium is our Long
1055 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='medium']/timeFormat/pattern");
1056 if (el != null)
1057 df.LongTimePattern = ConvertTimePatternFormat (el.InnerText);
1059 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='short']/timeFormat/pattern");
1060 if (el != null)
1061 df.ShortTimePattern = ConvertTimePatternFormat (el.InnerText);
1063 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='yyyyMMMM']");
1064 if (el != null)
1065 df.YearMonthPattern = ConvertDatePatternFormat (el.InnerText);
1067 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='MMMMdd']");
1068 if (el != null)
1069 df.MonthDayPattern = ConvertDatePatternFormat (el.InnerText);
1071 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='am']");
1072 if (el == null)
1073 // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1074 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='am']");
1076 switch (ci.Name) {
1077 case "en-AU":
1078 df.AMDesignator = "AM";
1079 break;
1080 default:
1081 if (el != null)
1082 df.AMDesignator = el.InnerText;
1083 break;
1086 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='pm']");
1087 if (el == null)
1088 // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1089 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='pm']");
1091 switch (ci.Name) {
1092 case "en-AU":
1093 df.PMDesignator = "PM";
1094 break;
1095 default:
1096 if (el != null)
1097 df.PMDesignator = el.InnerText;
1098 break;
1102 var ni = ci.NumberFormatEntry;
1104 node = doc.SelectSingleNode ("ldml/numbers/symbols");
1105 if (node != null) {
1106 el = node.SelectSingleNode ("decimal");
1107 if (el != null) {
1108 ni.NumberDecimalSeparator =
1109 ni.PercentDecimalSeparator = el.InnerText;
1112 el = node.SelectSingleNode ("plusSign");
1113 if (el != null)
1114 ni.PositiveSign = el.InnerText;
1116 el = node.SelectSingleNode ("minusSign");
1117 if (el != null) {
1118 // CLDR uses unicode negative sign for some culture (e.g sv, is, lt, don't kwnow why) but .net always
1119 // uses simple - sign
1120 if (el.InnerText == "\u2212") {
1121 ni.NegativeSign = "-";
1122 } else if (el.InnerText == "\u200F\u002D") {
1123 // Remove any right-to-left mark characters
1124 ni.NegativeSign = "-";
1125 } else
1126 ni.NegativeSign = el.InnerText;
1129 el = node.SelectSingleNode ("infinity");
1131 // We cannot use the value from CLDR because many broken
1132 // .NET serializers (e.g. JSON) use text value of NegativeInfinity
1133 // and different value would break interoperability with .NET
1134 var inf = GetInfinitySymbol (ci);
1135 if (inf != null)
1136 ni.InfinitySymbol = inf;
1137 else if (el != null && el.InnerText != "∞") {
1138 ni.InfinitySymbol = el.InnerText;
1141 el = node.SelectSingleNode ("perMille");
1142 if (el != null)
1143 ni.PerMilleSymbol = el.InnerText;
1145 el = node.SelectSingleNode ("nan");
1146 if (el != null)
1147 ni.NaNSymbol = el.InnerText;
1149 el = node.SelectSingleNode ("percentSign");
1150 if (el != null)
1151 ni.PercentSymbol = el.InnerText;
1155 string value = null;
1157 // .net has incorrect separators for some countries and we want to be compatible
1158 switch (ci.Name) {
1159 case "es-ES":
1160 // es-ES does not have group separator but .net has '.'
1161 value = ".";
1162 break;
1163 default:
1164 if (node != null) {
1165 el = node.SelectSingleNode ("group");
1166 if (el != null) {
1167 value = el.InnerText;
1171 break;
1174 if (value != null) {
1175 ni.NumberGroupSeparator =
1176 ni.PercentGroupSeparator =
1177 ni.CurrencyGroupSeparator = value;
1181 string GetInfinitySymbol (CultureInfoEntry ci)
1183 // TODO: Add more
1184 switch (ci.TwoLetterISOLanguageName) {
1185 case "ca":
1186 return "Infinit";
1187 case "cs":
1188 case "sk":
1189 return "+nekonečno";
1190 case "de":
1191 return "+unendlich";
1192 case "el":
1193 return "Άπειρο";
1194 case "es":
1195 case "gl":
1196 return "Infinito";
1197 case "it":
1198 case "pt":
1199 return "+Infinito";
1200 case "nl":
1201 return "oneindig";
1202 case "fr":
1203 case "tzm":
1204 return "+Infini";
1205 case "pl":
1206 return "+nieskończoność";
1207 case "ru":
1208 case "tg":
1209 return "бесконечность";
1210 case "sl":
1211 return "neskončnost";
1212 case "rm":
1213 return "+infinit";
1214 case "lv":
1215 return "bezgalība";
1216 case "lt":
1217 return "begalybė";
1218 case "eu":
1219 return "Infinitu";
1222 return null;
1225 static string ConvertDatePatternFormat (string format)
1228 // LDMR uses different characters for some fields
1229 // http://unicode.org/reports/tr35/#Date_Format_Patterns
1231 format = format.Replace ("EEEE", "dddd"); // The full name of the day of the week
1232 format = format.Replace ("LLLL", "MMMM"); // The full month name
1234 if (format.EndsWith (" y", StringComparison.Ordinal))
1235 format += "yyy";
1237 return format;
1240 static string ConvertTimePatternFormat (string format)
1242 format = format.Replace ("a", "tt"); // AM or PM
1243 return format;
1246 static void ProcessAllNodes (XmlNodeList list, IList<string> values, Action<IList<string>, string, string> convertor)
1248 foreach (XmlNode entry in list) {
1249 var index = entry.Attributes["type"].Value;
1250 var value = entry.InnerText;
1251 convertor (values, index, value);
1255 // All text indexes are 1-based
1256 static void AddOrReplaceValue (IList<string> list, string oneBasedIndex, string value)
1258 int index = int.Parse (oneBasedIndex);
1259 AddOrReplaceValue (list, index - 1, value);
1262 static readonly string[] day_types = new string[] { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
1264 static void AddOrReplaceDayValue (IList<string> list, string dayType, string value)
1266 int index = Array.IndexOf (day_types, dayType);
1267 AddOrReplaceValue (list, index, value);
1270 static void AddOrReplaceValue (IList<string> list, int index, string value)
1272 if (list.Count <= index)
1273 ((List<string>) list).AddRange (new string[index - list.Count + 1]);
1275 list[index] = value;
1278 sealed class LcidComparer : IComparer<CultureInfoEntry>
1280 public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1282 return x.LCID.CompareTo (y.LCID);
1286 sealed class ExportNameComparer : IComparer<CultureInfoEntry>
1288 public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1290 return String.Compare (x.GetExportName (), y.GetExportName (), StringComparison.OrdinalIgnoreCase);
1294 class RegionComparer : IComparer<RegionInfoEntry>
1296 public int Compare (RegionInfoEntry x, RegionInfoEntry y)
1298 return x.TwoLetterISORegionName.CompareTo (y.TwoLetterISORegionName);