Merge pull request #901 from Blewzman/FixAggregateExceptionGetBaseException
[mono.git] / tools / locale-builder / Driver.cs
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)
11 //
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:
19 //
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 //
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.
30 //
31
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;
40
41 namespace Mono.Tools.LocaleBuilder
42 {
43         public class Driver
44         {
45                 static readonly string data_root = Path.Combine ("CLDR", "common");
46
47                 public static void Main (string[] args)
48                 {
49                         Driver d = new Driver ();
50                         ParseArgs (args, d);
51                         d.Run ();
52                 }
53
54                 private static void ParseArgs (string[] args, Driver d)
55                 {
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;
65                         }
66                 }
67
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; 
75
76                 // The lang is the language that display names will be displayed in
77                 public string Lang
78                 {
79                         get
80                         {
81                                 if (lang == null)
82                                         lang = "en";
83                                 return lang;
84                         }
85                         set { lang = value; }
86                 }
87
88                 public string Locales
89                 {
90                         get { return locales; }
91                         set { locales = value; }
92                 }
93
94                 public string HeaderFileName
95                 {
96                         get
97                         {
98                                 if (header_name == null)
99                                         return "culture-info-tables.h";
100                                 return header_name;
101                         }
102                         set { header_name = value; }
103                 }
104
105                 public bool OutputCompare { get; set; }
106
107                 void Print ()
108                 {
109                         cultures.Sort ((a, b) => int.Parse (a.LCID.Substring (2), NumberStyles.HexNumber).CompareTo (int.Parse (b.LCID.Substring (2), NumberStyles.HexNumber)));
110
111                         var writer = Console.Out;
112
113                         foreach (var c in cultures) {
114                                 writer.WriteLine ("Name: {0}, LCID {1}", c.OriginalName, c.LCID);
115
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));
124
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);
148
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);
157
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);
187
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);
204                                 }
205
206                                 writer.WriteLine ();
207                         }
208                 }
209
210                 static Type GetCalendarType (CalendarType ct)
211                 {
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 ();
223                         }
224                 }
225
226                 static void Dump<T> (TextWriter tw, IList<T> values, string name, bool stopOnNull = false) where T : class
227                 {
228                         tw.Write (name);
229                         tw.Write (": ");
230
231                         for (int i = 0; i < values.Count; ++i) {
232                                 var v = values[i];
233
234                                 if (stopOnNull && v == null)
235                                         break;
236
237                                 if (i > 0)
238                                         tw.Write (", ");
239
240                                 tw.Write (v);
241                         }
242
243                         tw.WriteLine ();
244                 }
245
246                 void Run ()
247                 {
248                         Regex locales_regex = null;
249                         if (Locales != null)
250                                 locales_regex = new Regex (Locales);
251
252                         cultures = new List<CultureInfoEntry> ();
253                         var regions = new List<RegionInfoEntry> ();
254
255
256                         var supplemental = GetXmlDocument (Path.Combine (data_root, "supplemental", "supplementalData.xml"));
257
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);
263                         }
264
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;
269
270                                 if (parent == "root")
271                                         continue;
272
273                                 var locales = entry.Attributes["locales"].Value;
274                                 foreach (var locale in locales.Split (' '))
275                                         extra_parent_locales.Add (locale, parent);
276                         }
277
278                         var lcdids = GetXmlDocument ("lcids.xml");
279                         foreach (XmlNode lcid in lcdids.SelectNodes ("lcids/lcid")) {
280                                 var name = lcid.Attributes["name"].Value;
281
282                                 if (locales_regex != null && !locales_regex.IsMatch (name))
283                                         continue;
284
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 ();
294
295                                 if (!Import (ci, name))
296                                         continue;
297
298                                 cultures.Add (ci);
299                         }
300
301                         var doc_english = GetXmlDocument (Path.Combine (data_root, "main", "en.xml"));
302
303                         //
304                         // Fill all EnglishName values from en.xml language file
305                         //
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;
310
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;
316                                 }
317
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);
325                                         }
326                                 }
327
328                                 switch (ci.ThreeLetterWindowsLanguageName) {
329                                 case "CHT":
330                                         s = "Traditional";
331                                         break;
332                                 case "CHS":
333                                         s = "Simplified";
334                                         break;
335                                 }
336
337                                 if (s != null)
338                                         ci.EnglishName = string.Format ("{0} ({1})", ci.EnglishName, s);
339
340                                 // Special case legacy chinese
341                                 if (ci.OriginalName == "zh-CHS" || ci.OriginalName == "zh-CHT")
342                                         ci.EnglishName += " Legacy";
343
344                                 // Mono is not localized and supports english only, hence the name will always be same
345                                 ci.DisplayName = ci.EnglishName;
346                         }
347
348                         //
349                         // Fill culture hierarchy for easier data manipulation
350                         //
351                         foreach (var ci in cultures) {
352                                 foreach (var p in cultures.Where (l => ci.LCID == l.ParentLcid)) {
353                                         ci.Children.Add (p);
354                                 }
355                         }
356
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);
360                         }
361
362                         var territory2dayofweek = new Dictionary<string, DayOfWeek> (StringComparer.OrdinalIgnoreCase);
363                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/firstDay")) {
364
365                                 if (entry.Attributes ["alt"] != null)
366                                         continue;
367
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 ();
384                                 }
385
386                                 var territories = entry.Attributes["territories"].Value.Split ();
387                                 foreach (var t in territories) {
388                                         territory2dayofweek.Add (t, dow);
389                                 }
390                         }
391
392                         var territory2wr = new Dictionary<string, CalendarWeekRule> (StringComparer.OrdinalIgnoreCase);
393                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/minDays")) {
394                                 CalendarWeekRule rule;
395
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 ();
405                                 }
406
407                                 var territories = entry.Attributes["territories"].InnerText.Split ();
408                                 foreach (var t in territories)
409                                         territory2wr[t] = rule;
410                         }
411
412                         //
413                         // Fill all territory speficic data where territory is available
414                         //
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);
420                         }
421
422                         foreach (var ci in cultures) {
423                                 if (ci.Territory == null)
424                                         continue;
425
426                                 DayOfWeek value;
427                                 if (territory2dayofweek.TryGetValue (ci.Territory, out value)) {
428                                         ci.DateTimeFormatEntry.FirstDayOfWeek = (int) value;
429                                 }
430
431                                 CalendarWeekRule rule;
432                                 if (territory2wr.TryGetValue (ci.Territory, out rule)) {
433                                         ci.DateTimeFormatEntry.CalendarWeekRule = (int) rule;
434                                 }
435
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
445                                         };
446
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;
450
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;
454
455                                         region.ISOCurrencySymbol = region_currency[ci.Territory];
456
457                                         el = doc_english.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", region.ISOCurrencySymbol));
458                                         region.CurrencyEnglishName = el.InnerText;
459
460                                         if (non_metric.Contains (ci.Territory))
461                                                 region.IsMetric = false;
462
463                                         var lcdid_value = int.Parse (ci.LCID.Substring (2), NumberStyles.HexNumber);
464                                         Patterns.FillValues (lcdid_value, region);
465                                         regions.Add (region);
466                                 }
467
468                                 string fraction_value;
469                                 if (currency_fractions.TryGetValue (region.ISOCurrencySymbol, out fraction_value)) {
470                                         ci.NumberFormatEntry.CurrencyDecimalDigits = fraction_value;
471                                 }
472
473                                 ci.RegionInfoEntry = region;
474                         }
475
476                         //
477                         // Fill neutral cultures territory data
478                         //
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 ();
502
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);
508
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));
515                                                 }
516
517                                                 break;
518                                         }
519                                 }
520
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 ();
536
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);
542
543
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));
550                                                 }
551
552                                                 break;
553                                         }
554                                 }
555
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 "es":
566                                         case "fr":
567                                                 nfe.CurrencySymbol = "€";
568                                                 break;
569                                         case "pt":
570                                                 nfe.CurrencySymbol = "R$";
571                                                 break;
572                                         case "sv":
573                                                 nfe.CurrencySymbol = "kr";
574                                                 break;
575                                         case "ms":
576                                                 nfe.CurrencySymbol = "RM";
577                                                 break;
578                                         case "bn":
579                                                 nfe.CurrencySymbol = "টা";
580                                                 break;
581                                         case "sr-Cyrl":
582                                                 nfe.CurrencySymbol = "Дин.";
583                                                 break;
584                                         case "sr-Latn":
585                                         case "sr":
586                                                 nfe.CurrencySymbol = "Din.";
587                                                 break;
588                                         case "zh":
589                                                 nfe.CurrencySymbol = "¥";
590                                                 break;
591                                         case "zh-Hant":
592                                                 nfe.CurrencySymbol = "HK$";
593                                                 break;
594
595                                         default:
596                                                 var all_currencies = new List<string> ();
597                                                 GetAllChildrenValues (ci, all_currencies, l => l.NumberFormatEntry.CurrencySymbol);
598                                                 var children = all_currencies.Where (l => l != null).Distinct ().ToList ();
599
600                                                 if (children.Count == 1) {
601                                                         nfe.CurrencySymbol = children[0];
602                                                 } else if (children.Count == 0) {
603                                                         if (!ci.HasMissingLocale)
604                                                                 Console.WriteLine ("No currency data for `{0}'", ci.Name);
605
606
607                                                 } else {
608                                                         // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
609                                                         // We have to manually disambiguate the correct entry (which is artofficial anyway)
610                                                         throw new ApplicationException (string.Format ("Ambiguous currency data for `{0}'", ci.Name));
611                                                 }
612
613                                                 break;
614                                         }
615                                 }
616
617                                 if (nfe.CurrencyDecimalDigits == null) {
618                                         var all_digits = new List<string> ();
619                                         GetAllChildrenValues (ci, all_digits, l => l.NumberFormatEntry.CurrencyDecimalDigits);
620                                         var children = all_digits.Where (l => l != null).Distinct ().ToList ();
621
622                                         if (children.Count == 1) {
623                                                 nfe.CurrencyDecimalDigits = children[0];
624                                         } else if (children.Count == 0) {
625                                                 if (!ci.HasMissingLocale)
626                                                         Console.WriteLine ("No currency decimal digits data for `{0}'", ci.Name);
627
628                                                 nfe.CurrencyDecimalDigits = "2";
629                                         } else if (ci.IsNeutral) {
630                                                 nfe.CurrencyDecimalDigits = "2";
631                                         } else {
632                                                 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
633                                                 // We have to manually disambiguate the correct entry (which is artofficial anyway)
634                                                 throw new ApplicationException (string.Format ("Ambiguous currency decimal digits data for `{0}'", ci.Name));
635                                         }
636                                 }
637                         }
638
639                         if (OutputCompare)
640                                 Print ();
641
642                         regions.Sort (new RegionComparer ());
643                         for (int i = 0; i < regions.Count; ++i)
644                                 regions[i].Index = i;
645
646                         /**
647                          * Dump each table individually. Using StringBuilders
648                          * because it is easier to debug, should switch to just
649                          * writing to streams eventually.
650                          */
651                         using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
652                                 writer.NewLine = "\n";
653                                 writer.WriteLine ();
654                                 writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
655                                 writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
656                                 writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
657                                 writer.WriteLine ("\n");
658
659                                 writer.WriteLine ("#define NUM_CULTURE_ENTRIES {0}", cultures.Count);
660                                 writer.WriteLine ("#define NUM_REGION_ENTRIES {0}", regions.Count);
661
662                                 writer.WriteLine ("\n");
663
664                                 // Sort the cultures by lcid
665                                 cultures.Sort (new LcidComparer ());
666
667                                 StringBuilder builder = new StringBuilder ();
668                                 int row = 0;
669                                 int count = cultures.Count;
670                                 for (int i = 0; i < count; i++) {
671                                         CultureInfoEntry ci = cultures[i];
672                                         if (ci.DateTimeFormatEntry == null)
673                                                 continue;
674                                         ci.DateTimeFormatEntry.AppendTableRow (builder);
675                                         ci.DateTimeFormatEntry.Row = row++;
676                                         if (i + 1 < count)
677                                                 builder.Append (',');
678                                         builder.Append ('\n');
679                                 }
680
681                                 writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
682                                 writer.Write (builder);
683                                 writer.WriteLine ("};\n\n");
684
685                                 builder = new StringBuilder ();
686                                 row = 0;
687                                 for (int i = 0; i < count; i++) {
688                                         CultureInfoEntry ci = cultures[i];
689                                         if (ci.NumberFormatEntry == null)
690                                                 continue;
691                                         ci.NumberFormatEntry.AppendTableRow (builder);
692                                         ci.NumberFormatEntry.Row = row++;
693                                         if (i + 1 < count)
694                                                 builder.Append (',');
695                                         builder.Append ('\n');
696                                 }
697
698                                 writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
699                                 writer.Write (builder);
700                                 writer.WriteLine ("};\n\n");
701
702                                 builder = new StringBuilder ();
703                                 row = 0;
704                                 for (int i = 0; i < count; i++) {
705                                         CultureInfoEntry ci = cultures[i];
706                                         ci.AppendTableRow (builder);
707                                         ci.Row = row++;
708                                         if (i + 1 < count)
709                                                 builder.Append (',');
710                                         builder.Append ('\n');
711                                 }
712
713                                 writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
714                                 writer.Write (builder);
715                                 writer.WriteLine ("};\n\n");
716
717                                 cultures.Sort (new ExportNameComparer ()); // Sort based on name
718                                 builder = new StringBuilder ();
719                                 for (int i = 0; i < count; i++) {
720                                         CultureInfoEntry ci = cultures[i];
721                                         var name = ci.GetExportName ().ToLowerInvariant ();
722                                         builder.Append ("\t{" + Entry.EncodeStringIdx (name) + ", ");
723                                         builder.Append (ci.Row + "}");
724                                         if (i + 1 < count)
725                                                 builder.Append (',');
726
727                                         builder.AppendFormat ("\t /* {0} */", name);
728                                         builder.Append ('\n');
729                                 }
730
731                                 writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
732                                 writer.Write (builder);
733                                 writer.WriteLine ("};\n\n");
734
735                                 builder = new StringBuilder ();
736                                 int rcount = 0;
737                                 foreach (RegionInfoEntry r in regions) {
738                                         r.AppendTableRow (builder);
739                                         if (++rcount != regions.Count)
740                                                 builder.Append (',');
741
742                                         builder.Append ('\n');
743                                 }
744                                 writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
745                                 writer.Write (builder);
746                                 writer.WriteLine ("};\n\n");
747
748                                 builder = new StringBuilder ();
749                                 rcount = 0;
750                                 foreach (RegionInfoEntry ri in regions) {
751                                         builder.Append ("\t{" + Entry.EncodeStringIdx (ri.TwoLetterISORegionName) + ", ");
752                                         builder.Append (ri.Index + "}");
753                                         if (++rcount != regions.Count)
754                                                 builder.Append (',');
755                                         
756                                         builder.AppendFormat ("\t /* {0} */", ri.TwoLetterISORegionName);
757                                         builder.Append ('\n');
758                                 }
759
760                                 writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
761                                 writer.Write (builder);
762                                 writer.WriteLine ("};\n\n");
763
764                                 writer.WriteLine ("static const char locale_strings [] = {");
765                                 writer.Write (Entry.GetStrings ());
766                                 writer.WriteLine ("};\n\n");
767
768                                 writer.WriteLine ("#endif\n");
769                         }
770                 }
771
772                 static void GetAllChildrenValues<T> (CultureInfoEntry entry, List<T> values, Func<CultureInfoEntry, T> selector)
773                 {
774                         foreach (var e in entry.Children) {
775                                 if (e == entry)
776                                         continue;
777
778                                 values.Add (selector (e));
779
780                                 foreach (var e2 in e.Children) {
781                                         GetAllChildrenValues (e2, values, selector);
782                                 }
783                         }
784                 }
785
786                 static XmlDocument GetXmlDocument (string path)
787                 {
788                         var doc = new XmlDocument ();
789                         doc.Load (new XmlTextReader (path) { /*DtdProcessing = DtdProcessing.Ignore*/ } );
790                         return doc;
791                 }
792
793                 bool Import (CultureInfoEntry data, string locale)
794                 {
795                         string fname = null;
796                         var sep = locale.Split ('_');
797                         data.Language = sep[0];
798
799                         // CLDR strictly follow ISO names, .NET does not
800                         // Replace names where non-iso2 is used, e.g. Norway
801                         if (data.Language != data.TwoLetterISOLanguageName) {
802                                 locale = data.TwoLetterISOLanguageName;
803                                 if (sep.Length > 1) {
804                                         locale += string.Join ("_", sep.Skip (1));
805                                 }
806                         }
807
808                         // Convert broken Chinese names to correct one
809                         switch (locale) {
810                         case "zh_CHS":
811                                 locale = "zh_Hans";
812                                 break;
813                         case "zh_CHT":
814                                 locale = "zh_Hant";
815                                 break;
816                         case "zh_CN":
817                                 locale = "zh_Hans_CN";
818                                 break;
819                         case "zh_HK":
820                                 locale = "zh_Hant_HK";
821                                 break;
822                         case "zh_SG":
823                                 locale = "zh_Hans_SG";
824                                 break;
825                         case "zh_TW":
826                                 locale = "zh_Hant_TW";
827                                 break;
828                         case "zh_MO":
829                                 locale = "zh_Hant_MO";
830                                 break;
831                         }
832
833                         sep = locale.Split ('_');
834
835                         string full_name = Path.Combine (data_root, "main", locale + ".xml");
836                         if (!File.Exists (full_name)) {
837                                 Console.WriteLine ("Missing locale file for `{0}'", locale);
838
839                                 // We could fill default values but that's not as simple as it seems. For instance for non-neutral
840                                 // cultures the next part could be territory or not.
841                                 return false;
842                         } else {
843                                 XmlDocument doc = null;
844
845                                 /*
846                                  * Locale generation is done in several steps, first we
847                                  * read the root file which is the base invariant data
848                                  * then the supplemental root data, 
849                                  * then the language file, the supplemental languages
850                                  * file then the locale file, then the supplemental
851                                  * locale file. Values in each descending file can
852                                  * overwrite previous values.
853                                  */
854                                 foreach (var part in sep) {
855                                         if (fname != null)
856                                                 fname += "_";
857
858                                         fname += part;
859
860                                         XmlDocument xml;
861                                         string extra;
862                                         if (extra_parent_locales.TryGetValue (fname, out extra)) {
863                                                 xml = GetXmlDocument (Path.Combine (data_root, "main", extra + ".xml"));
864                                                 if (doc == null)
865                                                         doc = xml;
866
867                                                 Import (xml, data);
868                                         }
869
870                                         xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
871                                         if (doc == null)
872                                                 doc = xml;
873
874                                         Import (xml, data);
875                                 }
876
877                                 //
878                                 // Extract localized locale name from language xml file. Have to do it after both language and territory are read
879                                 //
880                                 var el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", data.Language));
881                                 if (el != null)
882                                         data.NativeName = el.InnerText;
883
884                                 if (data.Territory != null) {
885                                         el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", data.Territory));
886                                         if (el != null) {
887                                                 // TODO: Should read <localePattern>
888                                                 data.NativeName = string.Format ("{0} ({1})", data.NativeName, el.InnerText);
889                                                 data.NativeTerritoryName = el.InnerText;
890                                         }
891
892                                         string currency;
893                                         // We have territory now we have to run the process again to extract currency symbol
894                                         if (region_currency.TryGetValue (data.Territory, out currency)) {
895                                                 fname = null;
896
897                                                 var xml = GetXmlDocument (Path.Combine (data_root, "main", "root.xml"));
898                                                 el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
899                                                 if (el != null)
900                                                         data.NumberFormatEntry.CurrencySymbol = el.InnerText;
901
902                                                 foreach (var part in sep) {
903                                                         if (fname != null)
904                                                                 fname += "_";
905
906                                                         fname += part;
907
908                                                         xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
909                                                         el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
910                                                         if (el != null)
911                                                                 data.NumberFormatEntry.CurrencySymbol = el.InnerText;
912
913                                                         el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", currency));
914                                                         if (el != null)
915                                                                 data.NativeCurrencyName = el.InnerText;
916                                                 }
917                                         }
918                                 }
919                         }
920
921                         // It looks like it never changes
922                         data.DateTimeFormatEntry.TimeSeparator = ":";
923
924                         // TODO: Don't have input data available but most values are 2 with few exceptions for 1 and 3
925                         // We don't add 3 as it's for some arabic states only
926                         switch (data.ThreeLetterISOLanguageName) {
927                         case "amh":
928                                 data.NumberFormatEntry.NumberDecimalDigits =
929                                 data.NumberFormatEntry.PercentDecimalDigits = 1;
930                                 break;
931                         default:
932                                 data.NumberFormatEntry.NumberDecimalDigits =
933                                 data.NumberFormatEntry.PercentDecimalDigits = 2;
934                                 break;
935                         }
936
937                         // TODO: For now we capture only native name for default calendar
938                         data.NativeCalendarNames[((int) data.CalendarType & 0xFF) - 1] = data.DateTimeFormatEntry.NativeCalendarName;
939
940                         var lcdid_value = int.Parse (data.LCID.Substring (2), NumberStyles.HexNumber);
941                         Patterns.FillValues (lcdid_value, data);
942
943                         return true;
944                 }
945
946                 void Import (XmlDocument doc, CultureInfoEntry ci)
947                 {
948                         XmlNodeList nodes;
949                         XmlNode el;
950
951                         //
952                         // Extract script & teritory
953                         //
954                         el = doc.SelectSingleNode ("ldml/identity/script");
955                         if (el != null)
956                                 ci.Script = el.Attributes["type"].Value;
957
958                         el = doc.SelectSingleNode ("ldml/identity/territory");
959                         if (el != null)
960                                 ci.Territory = el.Attributes["type"].Value;
961
962                         var df = ci.DateTimeFormatEntry;
963
964                         string calendar;
965                         // Default calendar is for now always "gregorian"
966                         switch (ci.Name) {
967                         case "th": case "th-TH":
968                                 calendar = "buddhist";
969                                 ci.CalendarType = CalendarType.ThaiBuddhist; // typeof (ThaiBuddhistCalendar);
970                                 break;
971                         case "ar": case "ar-SA":
972                                 calendar = "islamic";
973                                 ci.CalendarType = CalendarType.UmAlQuraCalendar; // typeof (UmAlQuraCalendar);
974                                 break;
975                         case "ps": case "ps-AF": case "prs": case "prs-AF": case "dv": case "dv-MV":
976                                 calendar = "persian";
977                                 ci.CalendarType = CalendarType.HijriCalendar; // typeof (HijriCalendar);
978                                 break;
979                         default:
980                                 calendar = "gregorian";
981                                 ci.CalendarType = CalendarType.Gregorian; // typeof (GregorianCalendar);
982                                 ci.GregorianCalendarType = GregorianCalendarTypes.Localized;
983                                 break;
984                         }
985
986                         var node = doc.SelectSingleNode (string.Format ("ldml/dates/calendars/calendar[@type='{0}']", calendar));
987                         if (node != null) {
988                                 el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/types/type[@type='{0}']", calendar));
989                                 if (el != null)
990                                         df.NativeCalendarName = el.InnerText;
991
992
993                                 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='wide']"/>
994                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
995                                 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
996                                 nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='wide']/month");
997                                 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
998
999                                 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='abbreviated']"/>
1000                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='abbreviated']/month");
1001                                 ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
1002                                 nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='abbreviated']/month");
1003                                 ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
1004
1005                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
1006                                 if (nodes != null) {
1007                                         ProcessAllNodes (nodes, df.MonthGenitiveNames, AddOrReplaceValue);
1008                                 }
1009
1010                                 // All values seem to match
1011                                 Array.Copy (df.AbbreviatedMonthNames, df.AbbreviatedMonthGenitiveNames, df.AbbreviatedMonthNames.Length);
1012
1013                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='wide']/day");
1014                                 ProcessAllNodes (nodes, df.DayNames, AddOrReplaceDayValue);
1015
1016                                 // Apply global rule first <alias source="locale" path="../../dayContext[@type='format']/dayWidth[@type='abbreviated']"/>
1017                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='abbreviated']/day");
1018                                 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
1019                                 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='abbreviated']/day");
1020                                 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
1021
1022                                 // TODO: This is not really ShortestDayNames as .NET uses it
1023                                 // Apply global rules first <alias source="locale" path="../../dayContext[@type='stand-alone']/dayWidth[@type='narrow']"/>
1024                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='narrow']/day");
1025                                 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1026                                 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='narrow']/day");
1027                                 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1028 /*
1029                                 Cannot really be used it's too different to .NET and most app rely on it
1030  
1031                                 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='full']/dateFormat/pattern");
1032                                 if (el != null)
1033                                         df.LongDatePattern = ConvertDatePatternFormat (el.InnerText);
1034
1035                                 // Medium is our short
1036                                 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='medium']/dateFormat/pattern");
1037                                 if (el != null)
1038                                         df.ShortDatePattern = ConvertDatePatternFormat (el.InnerText);
1039
1040                                 // Medium is our Long
1041                                 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='medium']/timeFormat/pattern");
1042                                 if (el != null)
1043                                         df.LongTimePattern = ConvertTimePatternFormat (el.InnerText);
1044
1045                                 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='short']/timeFormat/pattern");
1046                                 if (el != null)
1047                                         df.ShortTimePattern = ConvertTimePatternFormat (el.InnerText);
1048
1049                                 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='yyyyMMMM']");
1050                                 if (el != null)
1051                                         df.YearMonthPattern = ConvertDatePatternFormat (el.InnerText);
1052
1053                                 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='MMMMdd']");
1054                                 if (el != null)
1055                                         df.MonthDayPattern = ConvertDatePatternFormat (el.InnerText);
1056 */
1057                                 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='am']");
1058                                 if (el == null)
1059                                         // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1060                                         el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='am']");
1061
1062                                 if (el != null)
1063                                         df.AMDesignator = el.InnerText;
1064
1065                                 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='pm']");
1066                                 if (el == null)
1067                                         // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1068                                         el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='pm']");
1069
1070                                 // No data
1071                                 if (el != null)
1072                                         df.PMDesignator = el.InnerText;
1073                         }
1074
1075                         var ni = ci.NumberFormatEntry;
1076
1077                         node = doc.SelectSingleNode ("ldml/numbers/symbols");
1078                         if (node != null) {
1079                                 el = node.SelectSingleNode ("decimal");
1080                                 if (el != null) {
1081                                         ni.NumberDecimalSeparator =
1082                                         ni.PercentDecimalSeparator = el.InnerText;
1083                                 }
1084
1085                                 el = node.SelectSingleNode ("plusSign");
1086                                 if (el != null)
1087                                         ni.PositiveSign = el.InnerText;
1088
1089                                 el = node.SelectSingleNode ("minusSign");
1090                                 if (el != null) {
1091                                         // CLDR uses unicode negative sign for some culture (e.g sv, is, lt, don't kwnow why) but .net always
1092                                         // uses simple - sign
1093                                         if (el.InnerText == "\u2212")
1094                                                 ni.NegativeSign = "-";
1095                                         else
1096                                                 ni.NegativeSign = el.InnerText;                                 
1097                                 }
1098
1099                                 el = node.SelectSingleNode ("infinity");
1100
1101                                 // We cannot use the value from CLDR because many broken
1102                                 // .NET serializers (e.g. JSON) use text value of NegativeInfinity
1103                                 // and different value would break interoperability with .NET
1104                                 var inf = GetInfinitySymbol (ci);
1105                                 if (inf != null)
1106                                         ni.InfinitySymbol = inf;
1107                                 else if (el != null && el.InnerText != "∞") {
1108                                         ni.InfinitySymbol = el.InnerText;
1109                                 }
1110
1111                                 el = node.SelectSingleNode ("perMille");
1112                                 if (el != null)
1113                                         ni.PerMilleSymbol = el.InnerText;
1114
1115                                 el = node.SelectSingleNode ("nan");
1116                                 if (el != null)
1117                                         ni.NaNSymbol = el.InnerText;
1118
1119                                 el = node.SelectSingleNode ("percentSign");
1120                                 if (el != null)
1121                                         ni.PercentSymbol = el.InnerText;
1122
1123                                 el = node.SelectSingleNode ("group");
1124                                 if (el != null) {
1125                                         ni.NumberGroupSeparator =
1126                                         ni.PercentGroupSeparator =
1127                                         ni.CurrencyGroupSeparator = el.InnerText;
1128                                 }
1129                         }
1130                 }
1131
1132                 string GetInfinitySymbol (CultureInfoEntry ci)
1133                 {
1134                         // TODO: Add more
1135                         switch (ci.TwoLetterISOLanguageName) {
1136                                 case "ca":
1137                                         return "Infinit";
1138                                 case "cs":
1139                                 case "sk":
1140                                         return "+nekonečno";
1141                                 case "de":
1142                                         return "+unendlich";
1143                                 case "el":
1144                                         return "Άπειρο";
1145                                 case "es":
1146                                 case "gl":
1147                                         return "Infinito";
1148                                 case "it":
1149                                 case "pt":
1150                                         return "+Infinito";
1151                                 case "nl":
1152                                         return "oneindig";
1153                                 case "fr":
1154                                 case "tzm":
1155                                         return "+Infini";
1156                                 case "pl":
1157                                         return "+nieskończoność";
1158                                 case "ru":
1159                                 case "tg":
1160                                         return "бесконечность";
1161                                 case "sl":
1162                                         return "neskončnost";
1163                                 case "rm":
1164                                         return "+infinit";
1165                                 case "lv":
1166                                         return "bezgalība";
1167                                 case "lt":
1168                                         return "begalybė";
1169                                 case "eu":
1170                                         return "Infinitu";
1171                         }
1172
1173                         return null;
1174                 }
1175
1176                 static string ConvertDatePatternFormat (string format)
1177                 {
1178                         //
1179                         // LDMR uses different characters for some fields
1180                         // http://unicode.org/reports/tr35/#Date_Format_Patterns
1181                         //
1182                         format = format.Replace ("EEEE", "dddd"); // The full name of the day of the week
1183                         format = format.Replace ("LLLL", "MMMM"); // The full month name
1184
1185                         if (format.EndsWith (" y", StringComparison.Ordinal))
1186                                 format += "yyy";
1187
1188                         return format;
1189                 }
1190
1191                 static string ConvertTimePatternFormat (string format)
1192                 {
1193                         format = format.Replace ("a", "tt"); // AM or PM
1194                         return format;
1195                 }
1196
1197                 static void ProcessAllNodes (XmlNodeList list, IList<string> values, Action<IList<string>, string, string> convertor)
1198                 {
1199                         foreach (XmlNode entry in list) {
1200                                 var index = entry.Attributes["type"].Value;
1201                                 var value = entry.InnerText;
1202                                 convertor (values, index, value);
1203                         }
1204                 }
1205
1206                 // All text indexes are 1-based
1207                 static void AddOrReplaceValue (IList<string> list, string oneBasedIndex, string value)
1208                 {
1209                         int index = int.Parse (oneBasedIndex);
1210                         AddOrReplaceValue (list, index - 1, value);
1211                 }
1212
1213                 static readonly string[] day_types = new string[] { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
1214
1215                 static void AddOrReplaceDayValue (IList<string> list, string dayType, string value)
1216                 {
1217                         int index = Array.IndexOf (day_types, dayType);
1218                         AddOrReplaceValue (list, index, value);
1219                 }
1220
1221                 static void AddOrReplaceValue (IList<string> list, int index, string value)
1222                 {
1223                         if (list.Count <= index)
1224                                 ((List<string>) list).AddRange (new string[index - list.Count + 1]);
1225
1226                         list[index] = value;
1227                 }
1228
1229                 sealed class LcidComparer : IComparer<CultureInfoEntry>
1230                 {
1231                         public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1232                         {
1233                                 return x.LCID.CompareTo (y.LCID);
1234                         }
1235                 }
1236
1237                 sealed class ExportNameComparer : IComparer<CultureInfoEntry>
1238                 {
1239                         public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1240                         {
1241                                 return String.Compare (x.GetExportName (), y.GetExportName (), StringComparison.OrdinalIgnoreCase);
1242                         }
1243                 }
1244
1245                 class RegionComparer : IComparer<RegionInfoEntry>
1246                 {
1247                         public int Compare (RegionInfoEntry x, RegionInfoEntry y)
1248                         {
1249                                 return x.TwoLetterISORegionName.CompareTo (y.TwoLetterISORegionName);
1250                         }
1251                 }
1252         }
1253 }