Merge pull request #618 from knocte/aspnet_lru
[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                                 DayOfWeek dow;
365
366                                 switch (entry.Attributes["day"].Value) {
367                                 case "mon":
368                                         dow = DayOfWeek.Monday;
369                                         break;
370                                 case "fri":
371                                         dow = DayOfWeek.Friday;
372                                         break;
373                                 case "sat":
374                                         dow = DayOfWeek.Saturday;
375                                         break;
376                                 case "sun":
377                                         dow = DayOfWeek.Sunday;
378                                         break;
379                                 default:
380                                         throw new NotImplementedException ();
381                                 }
382
383                                 var territories = entry.Attributes["territories"].Value.Split ();
384                                 foreach (var t in territories)
385                                         territory2dayofweek[t] = dow;
386                         }
387
388                         var territory2wr = new Dictionary<string, CalendarWeekRule> (StringComparer.OrdinalIgnoreCase);
389                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/minDays")) {
390                                 CalendarWeekRule rule;
391
392                                 switch (entry.Attributes["count"].InnerText) {
393                                 case "1":
394                                         rule = CalendarWeekRule.FirstDay;
395                                         break;
396                                 case "4":
397                                         rule = CalendarWeekRule.FirstFourDayWeek;
398                                         break;
399                                 default:
400                                         throw new NotImplementedException ();
401                                 }
402
403                                 var territories = entry.Attributes["territories"].InnerText.Split ();
404                                 foreach (var t in territories)
405                                         territory2wr[t] = rule;
406                         }
407
408                         //
409                         // Fill all territory speficic data where territory is available
410                         //
411                         var non_metric = new HashSet<string> ();
412                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/measurementData/measurementSystem[@type='US']")) {
413                                 var territories = entry.Attributes["territories"].InnerText.Split ();
414                                 foreach (var t in territories)
415                                         non_metric.Add (t);
416                         }
417
418                         foreach (var ci in cultures) {
419                                 if (ci.Territory == null)
420                                         continue;
421
422                                 DayOfWeek value;
423                                 if (territory2dayofweek.TryGetValue (ci.Territory, out value)) {
424                                         ci.DateTimeFormatEntry.FirstDayOfWeek = (int) value;
425                                 }
426
427                                 CalendarWeekRule rule;
428                                 if (territory2wr.TryGetValue (ci.Territory, out rule)) {
429                                         ci.DateTimeFormatEntry.CalendarWeekRule = (int) rule;
430                                 }
431
432                                 string fraction_value;
433                                 if (currency_fractions.TryGetValue (ci.Territory, out fraction_value)) {
434                                         ci.NumberFormatEntry.CurrencyDecimalDigits = fraction_value;
435                                 }
436
437                                 RegionInfoEntry region = regions.Where (l => l.Name == ci.Territory).FirstOrDefault ();
438                                 if (region == null) {
439                                         region = new RegionInfoEntry () {
440                                                 CurrencySymbol = ci.NumberFormatEntry.CurrencySymbol,
441                                                 EnglishName = ci.EnglishName,
442                                                 NativeName = ci.NativeTerritoryName,
443                                                 Name = ci.Territory,
444                                                 TwoLetterISORegionName = ci.Territory,
445                                                 CurrencyNativeName = ci.NativeCurrencyName
446                                         };
447
448                                         var tc = supplemental.SelectSingleNode (string.Format ("supplementalData/codeMappings/territoryCodes[@type='{0}']", ci.Territory));
449                                         region.ThreeLetterISORegionName = tc.Attributes["alpha3"].Value;
450                                         region.ThreeLetterWindowsRegionName = region.ThreeLetterISORegionName;
451
452                                         var el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci.Territory));
453                                         region.EnglishName = el.InnerText;
454                                         region.DisplayName = region.EnglishName;
455
456                                         region.ISOCurrencySymbol = region_currency[ci.Territory];
457
458                                         el = doc_english.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", region.ISOCurrencySymbol));
459                                         region.CurrencyEnglishName = el.InnerText;
460
461                                         if (non_metric.Contains (ci.Territory))
462                                                 region.IsMetric = false;
463
464                                         var lcdid_value = int.Parse (ci.LCID.Substring (2), NumberStyles.HexNumber);
465                                         Patterns.FillValues (lcdid_value, region);
466                                         regions.Add (region);
467                                 }
468
469                                 ci.RegionInfoEntry = region;
470                         }
471
472                         //
473                         // Fill neutral cultures territory data
474                         //
475                         foreach (var ci in cultures) {
476                                 var dtf = ci.DateTimeFormatEntry;
477                                 if (dtf.FirstDayOfWeek == null) {
478                                         switch (ci.Name) {
479                                         case "ar":
480                                                 dtf.FirstDayOfWeek = (int) DayOfWeek.Saturday;
481                                                 break;
482                                         case "en":
483                                         case "pt":
484                                         case "zh-Hans":
485                                                 dtf.FirstDayOfWeek = (int) DayOfWeek.Sunday;
486                                                 break;
487                                         case "es":
488                                         case "fr":
489                                         case "bn":
490                                         case "sr-Cyrl":
491                                         case "sr-Latn":
492                                                 dtf.FirstDayOfWeek = (int) DayOfWeek.Monday;
493                                                 break;
494                                         default:
495                                                 List<int?> all_fdow = new List<int?> ();
496                                                 GetAllChildrenValues (ci, all_fdow, l => l.DateTimeFormatEntry.FirstDayOfWeek);
497                                                 var children = all_fdow.Where (l => l != null).Distinct ().ToList ();
498
499                                                 if (children.Count == 1) {
500                                                         dtf.FirstDayOfWeek = children[0];
501                                                 } else if (children.Count == 0) {
502                                                         if (!ci.HasMissingLocale)
503                                                                 Console.WriteLine ("No week data for `{0}'", ci.Name);
504
505                                                         // Default to Sunday
506                                                         dtf.FirstDayOfWeek = (int) DayOfWeek.Sunday;
507                                                 } else {
508                                                         // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
509                                                         // We have to manually disambiguate the correct entry (which is artofficial anyway)
510                                                         throw new ApplicationException (string.Format ("Ambiguous week data for `{0}'", ci.Name));
511                                                 }
512
513                                                 break;
514                                         }
515                                 }
516
517                                 if (dtf.CalendarWeekRule == null) {
518                                         switch (ci.Name) {
519                                         case "ar":
520                                         case "en":
521                                         case "es":
522                                         case "zh-Hans":
523                                         case "pt":
524                                         case "fr":
525                                         case "bn":
526                                                 dtf.CalendarWeekRule = (int) CalendarWeekRule.FirstDay;
527                                                 break;
528                                         default:
529                                                 List<int?> all_cwr = new List<int?> ();
530                                                 GetAllChildrenValues (ci, all_cwr, l => l.DateTimeFormatEntry.CalendarWeekRule);
531                                                 var children = all_cwr.Where (l => l != null).Distinct ().ToList ();
532
533                                                 if (children.Count == 1) {
534                                                         dtf.CalendarWeekRule = children[0];
535                                                 } else if (children.Count == 0) {
536                                                         if (!ci.HasMissingLocale)
537                                                                 Console.WriteLine ("No calendar week data for `{0}'", ci.Name);
538
539
540                                                         // Default to FirstDay
541                                                         dtf.CalendarWeekRule = (int) CalendarWeekRule.FirstDay;
542                                                 } else {
543                                                         // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
544                                                         // We have to manually disambiguate the correct entry (which is artofficial anyway)
545                                                         throw new ApplicationException (string.Format ("Ambiguous calendar data for `{0}'", ci.Name));
546                                                 }
547
548                                                 break;
549                                         }
550                                 }
551
552                                 var nfe = ci.NumberFormatEntry;
553                                 if (nfe.CurrencySymbol == null) {
554                                         switch (ci.Name) {
555                                         case "ar":
556                                                 nfe.CurrencySymbol = "ر.س.‏";
557                                                 break;
558                                         case "en":
559                                                 nfe.CurrencySymbol = "$";
560                                                 break;
561                                         case "es":
562                                         case "fr":
563                                                 nfe.CurrencySymbol = "€";
564                                                 break;
565                                         case "pt":
566                                                 nfe.CurrencySymbol = "R$";
567                                                 break;
568                                         case "sv":
569                                                 nfe.CurrencySymbol = "kr";
570                                                 break;
571                                         case "ms":
572                                                 nfe.CurrencySymbol = "RM";
573                                                 break;
574                                         case "bn":
575                                                 nfe.CurrencySymbol = "টা";
576                                                 break;
577                                         case "sr-Cyrl":
578                                                 nfe.CurrencySymbol = "Дин.";
579                                                 break;
580                                         case "sr-Latn":
581                                         case "sr":
582                                                 nfe.CurrencySymbol = "Din.";
583                                                 break;
584                                         case "zh":
585                                                 nfe.CurrencySymbol = "¥";
586                                                 break;
587                                         case "zh-Hant":
588                                                 nfe.CurrencySymbol = "HK$";
589                                                 break;
590                                                 
591                                         default:
592                                                 var all_currencies = new List<string> ();
593                                                 GetAllChildrenValues (ci, all_currencies, l => l.NumberFormatEntry.CurrencySymbol);
594                                                 var children = all_currencies.Where (l => l != null).Distinct ().ToList ();
595
596                                                 if (children.Count == 1) {
597                                                         nfe.CurrencySymbol = children[0];
598                                                 } else if (children.Count == 0) {
599                                                         if (!ci.HasMissingLocale)
600                                                                 Console.WriteLine ("No currency data for `{0}'", ci.Name);
601
602
603                                                 } else {
604                                                         // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
605                                                         // We have to manually disambiguate the correct entry (which is artofficial anyway)
606                                                         throw new ApplicationException (string.Format ("Ambiguous currency data for `{0}'", ci.Name));
607                                                 }
608
609                                                 break;
610                                         }
611                                 }
612
613                                 if (nfe.CurrencyDecimalDigits == null) {
614                                         var all_digits = new List<string> ();
615                                         GetAllChildrenValues (ci, all_digits, l => l.NumberFormatEntry.CurrencyDecimalDigits);
616                                         var children = all_digits.Where (l => l != null).Distinct ().ToList ();
617
618                                         if (children.Count == 1) {
619                                                 nfe.CurrencyDecimalDigits = children[0];
620                                         } else if (children.Count == 0) {
621                                                 if (!ci.HasMissingLocale)
622                                                         Console.WriteLine ("No currency decimal digits data for `{0}'", ci.Name);
623
624                                                 nfe.CurrencyDecimalDigits = "2";
625                                         } else {
626                                                 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
627                                                 // We have to manually disambiguate the correct entry (which is artofficial anyway)
628                                                 throw new ApplicationException (string.Format ("Ambiguous currency decimal digits data for `{0}'", ci.Name));
629                                         }
630                                 }
631                         }
632
633                         if (OutputCompare)
634                                 Print ();
635
636                         regions.Sort (new RegionComparer ());
637                         for (int i = 0; i < regions.Count; ++i)
638                                 regions[i].Index = i;
639
640                         /**
641                          * Dump each table individually. Using StringBuilders
642                          * because it is easier to debug, should switch to just
643                          * writing to streams eventually.
644                          */
645                         using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
646                                 writer.NewLine = "\n";
647                                 writer.WriteLine ();
648                                 writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
649                                 writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
650                                 writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
651                                 writer.WriteLine ("\n");
652
653                                 writer.WriteLine ("#define NUM_CULTURE_ENTRIES {0}", cultures.Count);
654                                 writer.WriteLine ("#define NUM_REGION_ENTRIES {0}", regions.Count);
655
656                                 writer.WriteLine ("\n");
657
658                                 // Sort the cultures by lcid
659                                 cultures.Sort (new LcidComparer ());
660
661                                 StringBuilder builder = new StringBuilder ();
662                                 int row = 0;
663                                 int count = cultures.Count;
664                                 for (int i = 0; i < count; i++) {
665                                         CultureInfoEntry ci = cultures[i];
666                                         if (ci.DateTimeFormatEntry == null)
667                                                 continue;
668                                         ci.DateTimeFormatEntry.AppendTableRow (builder);
669                                         ci.DateTimeFormatEntry.Row = row++;
670                                         if (i + 1 < count)
671                                                 builder.Append (',');
672                                         builder.Append ('\n');
673                                 }
674
675                                 writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
676                                 writer.Write (builder);
677                                 writer.WriteLine ("};\n\n");
678
679                                 builder = new StringBuilder ();
680                                 row = 0;
681                                 for (int i = 0; i < count; i++) {
682                                         CultureInfoEntry ci = cultures[i];
683                                         if (ci.NumberFormatEntry == null)
684                                                 continue;
685                                         ci.NumberFormatEntry.AppendTableRow (builder);
686                                         ci.NumberFormatEntry.Row = row++;
687                                         if (i + 1 < count)
688                                                 builder.Append (',');
689                                         builder.Append ('\n');
690                                 }
691
692                                 writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
693                                 writer.Write (builder);
694                                 writer.WriteLine ("};\n\n");
695
696                                 builder = new StringBuilder ();
697                                 row = 0;
698                                 for (int i = 0; i < count; i++) {
699                                         CultureInfoEntry ci = cultures[i];
700                                         ci.AppendTableRow (builder);
701                                         ci.Row = row++;
702                                         if (i + 1 < count)
703                                                 builder.Append (',');
704                                         builder.Append ('\n');
705                                 }
706
707                                 writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
708                                 writer.Write (builder);
709                                 writer.WriteLine ("};\n\n");
710
711                                 cultures.Sort (new ExportNameComparer ()); // Sort based on name
712                                 builder = new StringBuilder ();
713                                 for (int i = 0; i < count; i++) {
714                                         CultureInfoEntry ci = cultures[i];
715                                         var name = ci.GetExportName ().ToLowerInvariant ();
716                                         builder.Append ("\t{" + Entry.EncodeStringIdx (name) + ", ");
717                                         builder.Append (ci.Row + "}");
718                                         if (i + 1 < count)
719                                                 builder.Append (',');
720
721                                         builder.AppendFormat ("\t /* {0} */", name);
722                                         builder.Append ('\n');
723                                 }
724
725                                 writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
726                                 writer.Write (builder);
727                                 writer.WriteLine ("};\n\n");
728
729                                 builder = new StringBuilder ();
730                                 int rcount = 0;
731                                 foreach (RegionInfoEntry r in regions) {
732                                         r.AppendTableRow (builder);
733                                         if (++rcount != regions.Count)
734                                                 builder.Append (',');
735
736                                         builder.Append ('\n');
737                                 }
738                                 writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
739                                 writer.Write (builder);
740                                 writer.WriteLine ("};\n\n");
741
742                                 builder = new StringBuilder ();
743                                 rcount = 0;
744                                 foreach (RegionInfoEntry ri in regions) {
745                                         builder.Append ("\t{" + Entry.EncodeStringIdx (ri.TwoLetterISORegionName) + ", ");
746                                         builder.Append (ri.Index + "}");
747                                         if (++rcount != regions.Count)
748                                                 builder.Append (',');
749                                         
750                                         builder.AppendFormat ("\t /* {0} */", ri.TwoLetterISORegionName);
751                                         builder.Append ('\n');
752                                 }
753
754                                 writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
755                                 writer.Write (builder);
756                                 writer.WriteLine ("};\n\n");
757
758                                 writer.WriteLine ("static const char locale_strings [] = {");
759                                 writer.Write (Entry.GetStrings ());
760                                 writer.WriteLine ("};\n\n");
761
762                                 writer.WriteLine ("#endif\n");
763                         }
764                 }
765
766                 static void GetAllChildrenValues<T> (CultureInfoEntry entry, List<T> values, Func<CultureInfoEntry, T> selector)
767                 {
768                         foreach (var e in entry.Children) {
769                                 if (e == entry)
770                                         continue;
771
772                                 values.Add (selector (e));
773
774                                 foreach (var e2 in e.Children) {
775                                         GetAllChildrenValues (e2, values, selector);
776                                 }
777                         }
778                 }
779
780                 static XmlDocument GetXmlDocument (string path)
781                 {
782                         var doc = new XmlDocument ();
783                         doc.Load (new XmlTextReader (path) { /*DtdProcessing = DtdProcessing.Ignore*/ } );
784                         return doc;
785                 }
786
787                 bool Import (CultureInfoEntry data, string locale)
788                 {
789                         string fname = null;
790                         var sep = locale.Split ('_');
791                         data.Language = sep[0];
792
793                         // CLDR strictly follow ISO names, .NET does not
794                         // Replace names where non-iso2 is used, e.g. Norway
795                         if (data.Language != data.TwoLetterISOLanguageName) {
796                                 locale = data.TwoLetterISOLanguageName;
797                                 if (sep.Length > 1) {
798                                         locale += string.Join ("_", sep.Skip (1));
799                                 }
800                         }
801
802                         // Convert broken Chinese names to correct one
803                         switch (locale) {
804                         case "zh_CHS":
805                                 locale = "zh_Hans";
806                                 break;
807                         case "zh_CHT":
808                                 locale = "zh_Hant";
809                                 break;
810                         case "zh_CN":
811                                 locale = "zh_Hans_CN";
812                                 break;
813                         case "zh_HK":
814                                 locale = "zh_Hant_HK";
815                                 break;
816                         case "zh_SG":
817                                 locale = "zh_Hans_SG";
818                                 break;
819                         case "zh_TW":
820                                 locale = "zh_Hant_TW";
821                                 break;
822                         case "zh_MO":
823                                 locale = "zh_Hant_MO";
824                                 break;
825                         }
826
827                         sep = locale.Split ('_');
828
829                         string full_name = Path.Combine (data_root, "main", locale + ".xml");
830                         if (!File.Exists (full_name)) {
831                                 Console.WriteLine ("Missing locale file for `{0}'", locale);
832
833                                 // We could fill default values but that's not as simple as it seems. For instance for non-neutral
834                                 // cultures the next part could be territory or not.
835                                 return false;
836                         } else {
837                                 XmlDocument doc = null;
838
839                                 /*
840                                  * Locale generation is done in several steps, first we
841                                  * read the root file which is the base invariant data
842                                  * then the supplemental root data, 
843                                  * then the language file, the supplemental languages
844                                  * file then the locale file, then the supplemental
845                                  * locale file. Values in each descending file can
846                                  * overwrite previous values.
847                                  */
848                                 foreach (var part in sep) {
849                                         if (fname != null)
850                                                 fname += "_";
851
852                                         fname += part;
853
854                                         XmlDocument xml;
855                                         string extra;
856                                         if (extra_parent_locales.TryGetValue (fname, out extra)) {
857                                                 xml = GetXmlDocument (Path.Combine (data_root, "main", extra + ".xml"));
858                                                 if (doc == null)
859                                                         doc = xml;
860
861                                                 Import (xml, data);
862                                         }
863
864                                         xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
865                                         if (doc == null)
866                                                 doc = xml;
867
868                                         Import (xml, data);
869                                 }
870
871                                 //
872                                 // Extract localized locale name from language xml file. Have to do it after both language and territory are read
873                                 //
874                                 var el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", data.Language));
875                                 if (el != null)
876                                         data.NativeName = el.InnerText;
877
878                                 if (data.Territory != null) {
879                                         el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", data.Territory));
880                                         if (el != null) {
881                                                 // TODO: Should read <localePattern>
882                                                 data.NativeName = string.Format ("{0} ({1})", data.NativeName, el.InnerText);
883                                                 data.NativeTerritoryName = el.InnerText;
884                                         }
885
886                                         string currency;
887                                         // We have territory now we have to run the process again to extract currency symbol
888                                         if (region_currency.TryGetValue (data.Territory, out currency)) {
889                                                 fname = null;
890
891                                                 var xml = GetXmlDocument (Path.Combine (data_root, "main", "root.xml"));
892                                                 el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
893                                                 if (el != null)
894                                                         data.NumberFormatEntry.CurrencySymbol = el.InnerText;
895
896                                                 foreach (var part in sep) {
897                                                         if (fname != null)
898                                                                 fname += "_";
899
900                                                         fname += part;
901
902                                                         xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
903                                                         el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
904                                                         if (el != null)
905                                                                 data.NumberFormatEntry.CurrencySymbol = el.InnerText;
906
907                                                         el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", currency));
908                                                         if (el != null)
909                                                                 data.NativeCurrencyName = el.InnerText;
910                                                 }
911                                         }
912                                 }
913                         }
914
915                         // It looks like it never changes
916                         data.DateTimeFormatEntry.TimeSeparator = ":";
917
918                         // TODO: Don't have input data available but most values are 2 with few exceptions for 1 and 3
919                         // We don't add 3 as it's for some arabic states only
920                         switch (data.ThreeLetterISOLanguageName) {
921                         case "amh":
922                                 data.NumberFormatEntry.NumberDecimalDigits =
923                                 data.NumberFormatEntry.PercentDecimalDigits = 1;
924                                 break;
925                         default:
926                                 data.NumberFormatEntry.NumberDecimalDigits =
927                                 data.NumberFormatEntry.PercentDecimalDigits = 2;
928                                 break;
929                         }
930
931                         // TODO: For now we capture only native name for default calendar
932                         data.NativeCalendarNames[((int) data.CalendarType & 0xFF) - 1] = data.DateTimeFormatEntry.NativeCalendarName;
933
934                         var lcdid_value = int.Parse (data.LCID.Substring (2), NumberStyles.HexNumber);
935                         Patterns.FillValues (lcdid_value, data);
936
937                         return true;
938                 }
939
940                 void Import (XmlDocument doc, CultureInfoEntry ci)
941                 {
942                         XmlNodeList nodes;
943                         XmlNode el;
944
945                         //
946                         // Extract script & teritory
947                         //
948                         el = doc.SelectSingleNode ("ldml/identity/script");
949                         if (el != null)
950                                 ci.Script = el.Attributes["type"].Value;
951
952                         el = doc.SelectSingleNode ("ldml/identity/territory");
953                         if (el != null)
954                                 ci.Territory = el.Attributes["type"].Value;
955
956                         var df = ci.DateTimeFormatEntry;
957
958                         string calendar;
959                         // Default calendar is for now always "gregorian"
960                         switch (ci.Name) {
961                         case "th": case "th-TH":
962                                 calendar = "buddhist";
963                                 ci.CalendarType = CalendarType.ThaiBuddhist; // typeof (ThaiBuddhistCalendar);
964                                 break;
965                         case "ar": case "ar-SA":
966                                 calendar = "islamic";
967                                 ci.CalendarType = CalendarType.UmAlQuraCalendar; // typeof (UmAlQuraCalendar);
968                                 break;
969                         case "ps": case "ps-AF": case "prs": case "prs-AF": case "dv": case "dv-MV":
970                                 calendar = "persian";
971                                 ci.CalendarType = CalendarType.HijriCalendar; // typeof (HijriCalendar);
972                                 break;
973                         default:
974                                 calendar = "gregorian";
975                                 ci.CalendarType = CalendarType.Gregorian; // typeof (GregorianCalendar);
976                                 ci.GregorianCalendarType = GregorianCalendarTypes.Localized;
977                                 break;
978                         }
979
980                         var node = doc.SelectSingleNode (string.Format ("ldml/dates/calendars/calendar[@type='{0}']", calendar));
981                         if (node != null) {
982                                 el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/types/type[@type='{0}']", calendar));
983                                 if (el != null)
984                                         df.NativeCalendarName = el.InnerText;
985
986
987                                 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='wide']"/>
988                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
989                                 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
990                                 nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='wide']/month");
991                                 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
992
993                                 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='abbreviated']"/>
994                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='abbreviated']/month");
995                                 ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
996                                 nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='abbreviated']/month");
997                                 ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
998
999                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
1000                                 if (nodes != null) {
1001                                         ProcessAllNodes (nodes, df.MonthGenitiveNames, AddOrReplaceValue);
1002                                 }
1003
1004                                 // All values seem to match
1005                                 Array.Copy (df.AbbreviatedMonthNames, df.AbbreviatedMonthGenitiveNames, df.AbbreviatedMonthNames.Length);
1006
1007                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='wide']/day");
1008                                 ProcessAllNodes (nodes, df.DayNames, AddOrReplaceDayValue);
1009
1010                                 // Apply global rule first <alias source="locale" path="../../dayContext[@type='format']/dayWidth[@type='abbreviated']"/>
1011                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='abbreviated']/day");
1012                                 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
1013                                 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='abbreviated']/day");
1014                                 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
1015
1016                                 // TODO: This is not really ShortestDayNames as .NET uses it
1017                                 // Apply global rules first <alias source="locale" path="../../dayContext[@type='stand-alone']/dayWidth[@type='narrow']"/>
1018                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='narrow']/day");
1019                                 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1020                                 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='narrow']/day");
1021                                 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1022 /*
1023                                 Cannot really be used it's too different to .NET and most app rely on it
1024  
1025                                 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='full']/dateFormat/pattern");
1026                                 if (el != null)
1027                                         df.LongDatePattern = ConvertDatePatternFormat (el.InnerText);
1028
1029                                 // Medium is our short
1030                                 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='medium']/dateFormat/pattern");
1031                                 if (el != null)
1032                                         df.ShortDatePattern = ConvertDatePatternFormat (el.InnerText);
1033
1034                                 // Medium is our Long
1035                                 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='medium']/timeFormat/pattern");
1036                                 if (el != null)
1037                                         df.LongTimePattern = ConvertTimePatternFormat (el.InnerText);
1038
1039                                 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='short']/timeFormat/pattern");
1040                                 if (el != null)
1041                                         df.ShortTimePattern = ConvertTimePatternFormat (el.InnerText);
1042
1043                                 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='yyyyMMMM']");
1044                                 if (el != null)
1045                                         df.YearMonthPattern = ConvertDatePatternFormat (el.InnerText);
1046
1047                                 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='MMMMdd']");
1048                                 if (el != null)
1049                                         df.MonthDayPattern = ConvertDatePatternFormat (el.InnerText);
1050 */
1051                                 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='am']");
1052                                 if (el == null)
1053                                         // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1054                                         el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='am']");
1055
1056                                 if (el != null)
1057                                         df.AMDesignator = el.InnerText;
1058
1059                                 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='pm']");
1060                                 if (el == null)
1061                                         // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1062                                         el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='pm']");
1063
1064                                 // No data
1065                                 if (el != null)
1066                                         df.PMDesignator = el.InnerText;
1067                         }
1068
1069                         var ni = ci.NumberFormatEntry;
1070
1071                         node = doc.SelectSingleNode ("ldml/numbers/symbols");
1072                         if (node != null) {
1073                                 el = node.SelectSingleNode ("decimal");
1074                                 if (el != null) {
1075                                         ni.NumberDecimalSeparator =
1076                                         ni.PercentDecimalSeparator =
1077                                         ni.CurrencyDecimalSeparator = el.InnerText;
1078                                 }
1079
1080                                 el = node.SelectSingleNode ("plusSign");
1081                                 if (el != null)
1082                                         ni.PositiveSign = el.InnerText;
1083
1084                                 el = node.SelectSingleNode ("minusSign");
1085                                 if (el != null)
1086                                         ni.NegativeSign = el.InnerText;
1087
1088                                 el = node.SelectSingleNode ("infinity");
1089
1090                                 // We cannot use the value from CLDR because many broken
1091                                 // .NET serializers (e.g. JSON) use text value of NegativeInfinity
1092                                 // and different value would break interoperability with .NET
1093                                 var inf = GetInfinitySymbol (ci);
1094                                 if (inf != null)
1095                                         ni.InfinitySymbol = inf;
1096                                 else if (el != null && el.InnerText != "∞") {
1097                                         ni.InfinitySymbol = el.InnerText;
1098                                 }
1099
1100                                 el = node.SelectSingleNode ("perMille");
1101                                 if (el != null)
1102                                         ni.PerMilleSymbol = el.InnerText;
1103
1104                                 el = node.SelectSingleNode ("nan");
1105                                 if (el != null)
1106                                         ni.NaNSymbol = el.InnerText;
1107
1108                                 el = node.SelectSingleNode ("percentSign");
1109                                 if (el != null)
1110                                         ni.PercentSymbol = el.InnerText;
1111
1112                                 el = node.SelectSingleNode ("group");
1113                                 if (el != null) {
1114                                         ni.NumberGroupSeparator =
1115                                         ni.PercentGroupSeparator =
1116                                         ni.CurrencyGroupSeparator = el.InnerText;
1117                                 }
1118                         }
1119                 }
1120
1121                 string GetInfinitySymbol (CultureInfoEntry ci)
1122                 {
1123                         // TODO: Add more
1124                         switch (ci.TwoLetterISOLanguageName) {
1125                                 case "ca":
1126                                         return "Infinit";
1127                                 case "cs":
1128                                 case "sk":
1129                                         return "+nekonečno";
1130                                 case "de":
1131                                         return "+unendlich";
1132                                 case "el":
1133                                         return "Άπειρο";
1134                                 case "es":
1135                                 case "gl":
1136                                         return "Infinito";
1137                                 case "it":
1138                                 case "pt":
1139                                         return "+Infinito";
1140                                 case "nl":
1141                                         return "oneindig";
1142                                 case "fr":
1143                                 case "tzm":
1144                                         return "+Infini";
1145                                 case "pl":
1146                                         return "+nieskończoność";
1147                                 case "ru":
1148                                 case "tg":
1149                                         return "бесконечность";
1150                                 case "sl":
1151                                         return "neskončnost";
1152                                 case "rm":
1153                                         return "+infinit";
1154                                 case "lv":
1155                                         return "bezgalība";
1156                                 case "lt":
1157                                         return "begalybė";
1158                                 case "eu":
1159                                         return "Infinitu";
1160                         }
1161
1162                         return null;
1163                 }
1164
1165                 static string ConvertDatePatternFormat (string format)
1166                 {
1167                         //
1168                         // LDMR uses different characters for some fields
1169                         // http://unicode.org/reports/tr35/#Date_Format_Patterns
1170                         //
1171                         format = format.Replace ("EEEE", "dddd"); // The full name of the day of the week
1172                         format = format.Replace ("LLLL", "MMMM"); // The full month name
1173
1174                         if (format.EndsWith (" y", StringComparison.Ordinal))
1175                                 format += "yyy";
1176
1177                         return format;
1178                 }
1179
1180                 static string ConvertTimePatternFormat (string format)
1181                 {
1182                         format = format.Replace ("a", "tt"); // AM or PM
1183                         return format;
1184                 }
1185
1186                 static void ProcessAllNodes (XmlNodeList list, IList<string> values, Action<IList<string>, string, string> convertor)
1187                 {
1188                         foreach (XmlNode entry in list) {
1189                                 var index = entry.Attributes["type"].Value;
1190                                 var value = entry.InnerText;
1191                                 convertor (values, index, value);
1192                         }
1193                 }
1194
1195                 // All text indexes are 1-based
1196                 static void AddOrReplaceValue (IList<string> list, string oneBasedIndex, string value)
1197                 {
1198                         int index = int.Parse (oneBasedIndex);
1199                         AddOrReplaceValue (list, index - 1, value);
1200                 }
1201
1202                 static readonly string[] day_types = new string[] { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
1203
1204                 static void AddOrReplaceDayValue (IList<string> list, string dayType, string value)
1205                 {
1206                         int index = Array.IndexOf (day_types, dayType);
1207                         AddOrReplaceValue (list, index, value);
1208                 }
1209
1210                 static void AddOrReplaceValue (IList<string> list, int index, string value)
1211                 {
1212                         if (list.Count <= index)
1213                                 ((List<string>) list).AddRange (new string[index - list.Count + 1]);
1214
1215                         list[index] = value;
1216                 }
1217
1218                 sealed class LcidComparer : IComparer<CultureInfoEntry>
1219                 {
1220                         public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1221                         {
1222                                 return x.LCID.CompareTo (y.LCID);
1223                         }
1224                 }
1225
1226                 sealed class ExportNameComparer : IComparer<CultureInfoEntry>
1227                 {
1228                         public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1229                         {
1230                                 return String.Compare (x.GetExportName (), y.GetExportName (), StringComparison.OrdinalIgnoreCase);
1231                         }
1232                 }
1233
1234                 class RegionComparer : IComparer<RegionInfoEntry>
1235                 {
1236                         public int Compare (RegionInfoEntry x, RegionInfoEntry y)
1237                         {
1238                                 return x.TwoLetterISORegionName.CompareTo (y.TwoLetterISORegionName);
1239                         }
1240                 }
1241         }
1242 }