Merge pull request #1659 from alexanderkyte/stringbuilder-referencesource
[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 "bs":
566                                                 nfe.CurrencySymbol = "KM";
567                                                 break;
568                                         case "es":
569                                         case "fr":
570                                         case "de":
571                                         case "it":
572                                         case "se":
573                                                 nfe.CurrencySymbol = "€";
574                                                 break;
575                                         case "hr":
576                                                 nfe.CurrencySymbol = "kn";
577                                                 break;                          
578                                         case "pt":
579                                                 nfe.CurrencySymbol = "R$";
580                                                 break;
581                                         case "sv":
582                                                 nfe.CurrencySymbol = "kr";
583                                                 break;
584                                         case "ms":
585                                                 nfe.CurrencySymbol = "RM";
586                                                 break;
587                                         case "bn":
588                                                 nfe.CurrencySymbol = "টা";
589                                                 break;
590                                         case "sr-Cyrl":
591                                                 nfe.CurrencySymbol = "Дин.";
592                                                 break;
593                                         case "sr-Latn":
594                                         case "sr":
595                                                 nfe.CurrencySymbol = "Din.";
596                                                 break;
597                                         case "zh":
598                                         case "zh-Hans":
599                                                 nfe.CurrencySymbol = "¥";
600                                                 break;
601                                         case "zh-Hant":
602                                                 nfe.CurrencySymbol = "HK$";
603                                                 break;
604
605                                         default:
606                                                 var all_currencies = new List<string> ();
607                                                 GetAllChildrenValues (ci, all_currencies, l => l.NumberFormatEntry.CurrencySymbol);
608                                                 var children = all_currencies.Where (l => l != null).Distinct ().ToList ();
609
610                                                 if (children.Count == 1) {
611                                                         nfe.CurrencySymbol = children[0];
612                                                 } else if (children.Count == 0) {
613                                                         if (!ci.HasMissingLocale)
614                                                                 Console.WriteLine ("No currency data for `{0}'", ci.Name);
615
616
617                                                 } else {
618                                                         // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
619                                                         // We have to manually disambiguate the correct entry (which is artofficial anyway)
620                                                         throw new ApplicationException (string.Format ("Ambiguous currency data for `{0}'. Possible values '{1}'", ci.Name, string.Join (", ", children)));
621                                                 }
622
623                                                 break;
624                                         }
625                                 }
626
627                                 if (nfe.CurrencyDecimalDigits == null) {
628                                         var all_digits = new List<string> ();
629                                         GetAllChildrenValues (ci, all_digits, l => l.NumberFormatEntry.CurrencyDecimalDigits);
630                                         var children = all_digits.Where (l => l != null).Distinct ().ToList ();
631
632                                         if (children.Count == 1) {
633                                                 nfe.CurrencyDecimalDigits = children[0];
634                                         } else if (children.Count == 0) {
635                                                 if (!ci.HasMissingLocale)
636                                                         Console.WriteLine ("No currency decimal digits data for `{0}'", ci.Name);
637
638                                                 nfe.CurrencyDecimalDigits = "2";
639                                         } else if (ci.IsNeutral) {
640                                                 nfe.CurrencyDecimalDigits = "2";
641                                         } else {
642                                                 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
643                                                 // We have to manually disambiguate the correct entry (which is artofficial anyway)
644                                                 throw new ApplicationException (string.Format ("Ambiguous currency decimal digits data for `{0}'", ci.Name));
645                                         }
646                                 }
647                         }
648
649                         if (OutputCompare)
650                                 Print ();
651
652                         regions.Sort (new RegionComparer ());
653                         for (int i = 0; i < regions.Count; ++i)
654                                 regions[i].Index = i;
655
656                         /**
657                          * Dump each table individually. Using StringBuilders
658                          * because it is easier to debug, should switch to just
659                          * writing to streams eventually.
660                          */
661                         using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
662                                 writer.NewLine = "\n";
663                                 writer.WriteLine ();
664                                 writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
665                                 writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
666                                 writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
667                                 writer.WriteLine ("\n");
668
669                                 writer.WriteLine ("#define NUM_CULTURE_ENTRIES {0}", cultures.Count);
670                                 writer.WriteLine ("#define NUM_REGION_ENTRIES {0}", regions.Count);
671
672                                 writer.WriteLine ("\n");
673
674                                 // Sort the cultures by lcid
675                                 cultures.Sort (new LcidComparer ());
676
677                                 StringBuilder builder = new StringBuilder ();
678                                 int row = 0;
679                                 int count = cultures.Count;
680                                 for (int i = 0; i < count; i++) {
681                                         CultureInfoEntry ci = cultures[i];
682                                         if (ci.DateTimeFormatEntry == null)
683                                                 continue;
684                                         ci.DateTimeFormatEntry.AppendTableRow (builder);
685                                         ci.DateTimeFormatEntry.Row = row++;
686                                         if (i + 1 < count)
687                                                 builder.Append (',');
688                                         builder.Append ('\n');
689                                 }
690
691                                 writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
692                                 writer.Write (builder);
693                                 writer.WriteLine ("};\n\n");
694
695                                 builder = new StringBuilder ();
696                                 row = 0;
697                                 for (int i = 0; i < count; i++) {
698                                         CultureInfoEntry ci = cultures[i];
699                                         if (ci.NumberFormatEntry == null)
700                                                 continue;
701                                         ci.NumberFormatEntry.AppendTableRow (builder);
702                                         ci.NumberFormatEntry.Row = row++;
703                                         if (i + 1 < count)
704                                                 builder.Append (',');
705                                         builder.Append ('\n');
706                                 }
707
708                                 writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
709                                 writer.Write (builder);
710                                 writer.WriteLine ("};\n\n");
711
712                                 builder = new StringBuilder ();
713                                 row = 0;
714                                 for (int i = 0; i < count; i++) {
715                                         CultureInfoEntry ci = cultures[i];
716                                         ci.AppendTableRow (builder);
717                                         ci.Row = row++;
718                                         if (i + 1 < count)
719                                                 builder.Append (',');
720                                         builder.Append ('\n');
721                                 }
722
723                                 writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
724                                 writer.Write (builder);
725                                 writer.WriteLine ("};\n\n");
726
727                                 cultures.Sort (new ExportNameComparer ()); // Sort based on name
728                                 builder = new StringBuilder ();
729                                 for (int i = 0; i < count; i++) {
730                                         CultureInfoEntry ci = cultures[i];
731                                         var name = ci.GetExportName ().ToLowerInvariant ();
732                                         builder.Append ("\t{" + Entry.EncodeStringIdx (name) + ", ");
733                                         builder.Append (ci.Row + "}");
734                                         if (i + 1 < count)
735                                                 builder.Append (',');
736
737                                         builder.AppendFormat ("\t /* {0} */", name);
738                                         builder.Append ('\n');
739                                 }
740
741                                 writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
742                                 writer.Write (builder);
743                                 writer.WriteLine ("};\n\n");
744
745                                 builder = new StringBuilder ();
746                                 int rcount = 0;
747                                 foreach (RegionInfoEntry r in regions) {
748                                         r.AppendTableRow (builder);
749                                         if (++rcount != regions.Count)
750                                                 builder.Append (',');
751
752                                         builder.Append ('\n');
753                                 }
754                                 writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
755                                 writer.Write (builder);
756                                 writer.WriteLine ("};\n\n");
757
758                                 builder = new StringBuilder ();
759                                 rcount = 0;
760                                 foreach (RegionInfoEntry ri in regions) {
761                                         builder.Append ("\t{" + Entry.EncodeStringIdx (ri.TwoLetterISORegionName) + ", ");
762                                         builder.Append (ri.Index + "}");
763                                         if (++rcount != regions.Count)
764                                                 builder.Append (',');
765                                         
766                                         builder.AppendFormat ("\t /* {0} */", ri.TwoLetterISORegionName);
767                                         builder.Append ('\n');
768                                 }
769
770                                 writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
771                                 writer.Write (builder);
772                                 writer.WriteLine ("};\n\n");
773
774                                 writer.WriteLine ("static const char locale_strings [] = {");
775                                 writer.Write (Entry.GetStrings ());
776                                 writer.WriteLine ("};\n\n");
777
778                                 writer.WriteLine ("#endif\n");
779                         }
780                 }
781
782                 static void GetAllChildrenValues<T> (CultureInfoEntry entry, List<T> values, Func<CultureInfoEntry, T> selector)
783                 {
784                         foreach (var e in entry.Children) {
785                                 if (e == entry)
786                                         continue;
787
788                                 values.Add (selector (e));
789
790                                 foreach (var e2 in e.Children) {
791                                         GetAllChildrenValues (e2, values, selector);
792                                 }
793                         }
794                 }
795
796                 static XmlDocument GetXmlDocument (string path)
797                 {
798                         var doc = new XmlDocument ();
799                         doc.Load (new XmlTextReader (path) { /*DtdProcessing = DtdProcessing.Ignore*/ } );
800                         return doc;
801                 }
802
803                 bool Import (CultureInfoEntry data, string locale)
804                 {
805                         string fname = null;
806                         var sep = locale.Split ('_');
807                         data.Language = sep[0];
808
809                         // CLDR strictly follow ISO names, .NET does not
810                         // Replace names where non-iso2 is used, e.g. Norway
811                         if (data.Language != data.TwoLetterISOLanguageName) {
812                                 locale = data.TwoLetterISOLanguageName;
813                                 if (sep.Length > 1) {
814                                         locale += string.Join ("_", sep.Skip (1));
815                                 }
816                         }
817
818                         // Convert broken Chinese names to correct one
819                         switch (locale) {
820                         case "zh_CHS":
821                                 locale = "zh_Hans";
822                                 break;
823                         case "zh_CHT":
824                                 locale = "zh_Hant";
825                                 break;
826                         case "zh_CN":
827                                 locale = "zh_Hans_CN";
828                                 break;
829                         case "zh_HK":
830                                 locale = "zh_Hant_HK";
831                                 break;
832                         case "zh_SG":
833                                 locale = "zh_Hans_SG";
834                                 break;
835                         case "zh_TW":
836                                 locale = "zh_Hant_TW";
837                                 break;
838                         case "zh_MO":
839                                 locale = "zh_Hant_MO";
840                                 break;
841                         }
842
843                         sep = locale.Split ('_');
844
845                         string full_name = Path.Combine (data_root, "main", locale + ".xml");
846                         if (!File.Exists (full_name)) {
847                                 Console.WriteLine ("Missing locale file for `{0}'", locale);
848
849                                 // We could fill default values but that's not as simple as it seems. For instance for non-neutral
850                                 // cultures the next part could be territory or not.
851                                 return false;
852                         } else {
853                                 XmlDocument doc = null;
854
855                                 /*
856                                  * Locale generation is done in several steps, first we
857                                  * read the root file which is the base invariant data
858                                  * then the supplemental root data, 
859                                  * then the language file, the supplemental languages
860                                  * file then the locale file, then the supplemental
861                                  * locale file. Values in each descending file can
862                                  * overwrite previous values.
863                                  */
864                                 foreach (var part in sep) {
865                                         if (fname != null)
866                                                 fname += "_";
867
868                                         fname += part;
869
870                                         XmlDocument xml;
871                                         string extra;
872                                         if (extra_parent_locales.TryGetValue (fname, out extra)) {
873                                                 xml = GetXmlDocument (Path.Combine (data_root, "main", extra + ".xml"));
874                                                 if (doc == null)
875                                                         doc = xml;
876
877                                                 Import (xml, data);
878                                         }
879
880                                         xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
881                                         if (doc == null)
882                                                 doc = xml;
883
884                                         Import (xml, data);
885                                 }
886
887                                 //
888                                 // Extract localized locale name from language xml file. Have to do it after both language and territory are read
889                                 //
890                                 var el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", data.Language));
891                                 if (el != null)
892                                         data.NativeName = el.InnerText;
893
894                                 if (data.Territory != null) {
895                                         el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", data.Territory));
896                                         if (el != null) {
897                                                 // TODO: Should read <localePattern>
898                                                 data.NativeName = string.Format ("{0} ({1})", data.NativeName, el.InnerText);
899                                                 data.NativeTerritoryName = el.InnerText;
900                                         }
901
902                                         string currency;
903                                         // We have territory now we have to run the process again to extract currency symbol
904                                         if (region_currency.TryGetValue (data.Territory, out currency)) {
905                                                 fname = null;
906
907                                                 var xml = GetXmlDocument (Path.Combine (data_root, "main", "root.xml"));
908                                                 el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
909                                                 if (el != null)
910                                                         data.NumberFormatEntry.CurrencySymbol = el.InnerText;
911
912                                                 foreach (var part in sep) {
913                                                         if (fname != null)
914                                                                 fname += "_";
915
916                                                         fname += part;
917
918                                                         xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
919                                                         el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
920                                                         if (el != null)
921                                                                 data.NumberFormatEntry.CurrencySymbol = el.InnerText;
922
923                                                         el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", currency));
924                                                         if (el != null)
925                                                                 data.NativeCurrencyName = el.InnerText;
926                                                 }
927                                         }
928                                 }
929                         }
930
931                         // It looks like it never changes
932                         data.DateTimeFormatEntry.TimeSeparator = ":";
933
934                         // TODO: Don't have input data available but most values are 2 with few exceptions for 1 and 3
935                         // We don't add 3 as it's for some arabic states only
936                         switch (data.ThreeLetterISOLanguageName) {
937                         case "amh":
938                                 data.NumberFormatEntry.NumberDecimalDigits =
939                                 data.NumberFormatEntry.PercentDecimalDigits = 1;
940                                 break;
941                         default:
942                                 data.NumberFormatEntry.NumberDecimalDigits =
943                                 data.NumberFormatEntry.PercentDecimalDigits = 2;
944                                 break;
945                         }
946
947                         // TODO: For now we capture only native name for default calendar
948                         data.NativeCalendarNames[((int) data.CalendarType & 0xFF) - 1] = data.DateTimeFormatEntry.NativeCalendarName;
949
950                         var lcdid_value = int.Parse (data.LCID.Substring (2), NumberStyles.HexNumber);
951                         Patterns.FillValues (lcdid_value, data);
952
953                         return true;
954                 }
955
956                 void Import (XmlDocument doc, CultureInfoEntry ci)
957                 {
958                         XmlNodeList nodes;
959                         XmlNode el;
960
961                         //
962                         // Extract script & teritory
963                         //
964                         el = doc.SelectSingleNode ("ldml/identity/script");
965                         if (el != null)
966                                 ci.Script = el.Attributes["type"].Value;
967
968                         el = doc.SelectSingleNode ("ldml/identity/territory");
969                         if (el != null)
970                                 ci.Territory = el.Attributes["type"].Value;
971
972                         var df = ci.DateTimeFormatEntry;
973
974                         string calendar;
975                         // Default calendar is for now always "gregorian"
976                         switch (ci.OriginalName) {
977                         case "th": case "th-TH":
978                                 calendar = "buddhist";
979                                 ci.CalendarType = CalendarType.ThaiBuddhist; // typeof (ThaiBuddhistCalendar);
980                                 break;
981                         case "ar": case "ar-SA":
982                                 calendar = "islamic";
983                                 ci.CalendarType = CalendarType.UmAlQuraCalendar; // typeof (UmAlQuraCalendar);
984                                 break;
985                         case "ps": case "ps-AF": case "prs": case "prs-AF": case "dv": case "dv-MV":
986                                 calendar = "persian";
987                                 ci.CalendarType = CalendarType.HijriCalendar; // typeof (HijriCalendar);
988                                 break;
989                         default:
990                                 calendar = "gregorian";
991                                 ci.CalendarType = CalendarType.Gregorian; // typeof (GregorianCalendar);
992                                 ci.GregorianCalendarType = GregorianCalendarTypes.Localized;
993                                 break;
994                         }
995
996                         var node = doc.SelectSingleNode (string.Format ("ldml/dates/calendars/calendar[@type='{0}']", calendar));
997                         if (node != null) {
998                                 el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/types/type[@type='{0}']", calendar));
999                                 if (el != null)
1000                                         df.NativeCalendarName = el.InnerText;
1001
1002
1003                                 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='wide']"/>
1004                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
1005                                 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
1006                                 nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='wide']/month");
1007                                 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
1008
1009                                 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='abbreviated']"/>
1010                                 if (ci.Name == "ja" || ci.Name == "ja-JP") {
1011                                         // Use common number style
1012                                 } else {
1013                                         nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='abbreviated']/month");
1014                                         ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
1015                                         nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='abbreviated']/month");
1016                                         ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
1017                                 }
1018
1019                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
1020                                 if (nodes != null) {
1021                                         ProcessAllNodes (nodes, df.MonthGenitiveNames, AddOrReplaceValue);
1022                                 }
1023
1024                                 // All values seem to match
1025                                 Array.Copy (df.AbbreviatedMonthNames, df.AbbreviatedMonthGenitiveNames, df.AbbreviatedMonthNames.Length);
1026
1027                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='wide']/day");
1028                                 ProcessAllNodes (nodes, df.DayNames, AddOrReplaceDayValue);
1029
1030                                 // Apply global rule first <alias source="locale" path="../../dayContext[@type='format']/dayWidth[@type='abbreviated']"/>
1031                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='abbreviated']/day");
1032                                 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
1033                                 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='abbreviated']/day");
1034                                 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
1035
1036                                 // TODO: This is not really ShortestDayNames as .NET uses it
1037                                 // Apply global rules first <alias source="locale" path="../../dayContext[@type='stand-alone']/dayWidth[@type='narrow']"/>
1038                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='narrow']/day");
1039                                 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1040                                 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='narrow']/day");
1041                                 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1042 /*
1043                                 Cannot really be used it's too different to .NET and most app rely on it
1044  
1045                                 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='full']/dateFormat/pattern");
1046                                 if (el != null)
1047                                         df.LongDatePattern = ConvertDatePatternFormat (el.InnerText);
1048
1049                                 // Medium is our short
1050                                 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='medium']/dateFormat/pattern");
1051                                 if (el != null)
1052                                         df.ShortDatePattern = ConvertDatePatternFormat (el.InnerText);
1053
1054                                 // Medium is our Long
1055                                 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='medium']/timeFormat/pattern");
1056                                 if (el != null)
1057                                         df.LongTimePattern = ConvertTimePatternFormat (el.InnerText);
1058
1059                                 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='short']/timeFormat/pattern");
1060                                 if (el != null)
1061                                         df.ShortTimePattern = ConvertTimePatternFormat (el.InnerText);
1062
1063                                 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='yyyyMMMM']");
1064                                 if (el != null)
1065                                         df.YearMonthPattern = ConvertDatePatternFormat (el.InnerText);
1066
1067                                 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='MMMMdd']");
1068                                 if (el != null)
1069                                         df.MonthDayPattern = ConvertDatePatternFormat (el.InnerText);
1070 */
1071                                 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='am']");
1072                                 if (el == null)
1073                                         // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1074                                         el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='am']");
1075
1076                                 // Manual edits for exact .net compatiblity
1077                                 switch (ci.Name) {
1078                                 case "en-AU":
1079                                         df.AMDesignator = "AM";
1080                                         break;
1081                                 case "en-NZ":
1082                                         df.AMDesignator = "a.m.";
1083                                         break;
1084                                 default:
1085                                         if (el != null)
1086                                                 df.AMDesignator = el.InnerText;
1087                                         break;
1088                                 }
1089
1090                                 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='pm']");
1091                                 if (el == null)
1092                                         // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1093                                         el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='pm']");
1094
1095                                 switch (ci.Name) {
1096                                 case "en-AU":
1097                                         df.PMDesignator = "PM";
1098                                         break;
1099                                 case "en-NZ":
1100                                         df.PMDesignator = "p.m.";
1101                                         break;
1102                                 default:
1103                                         if (el != null)
1104                                                 df.PMDesignator = el.InnerText;
1105                                         break;
1106                                 }
1107                         }
1108
1109                         var ni = ci.NumberFormatEntry;
1110
1111                         node = doc.SelectSingleNode ("ldml/numbers/symbols");
1112                         if (node != null) {
1113                                 el = node.SelectSingleNode ("decimal");
1114                                 if (el != null) {
1115                                         ni.NumberDecimalSeparator =
1116                                         ni.PercentDecimalSeparator = el.InnerText;
1117                                 }
1118
1119                                 el = node.SelectSingleNode ("plusSign");
1120                                 if (el != null)
1121                                         ni.PositiveSign = el.InnerText;
1122
1123                                 el = node.SelectSingleNode ("minusSign");
1124                                 if (el != null) {
1125                                         // CLDR uses unicode negative sign for some culture (e.g sv, is, lt, don't kwnow why) but .net always
1126                                         // uses simple - sign
1127                                         if (el.InnerText == "\u2212") {
1128                                                 ni.NegativeSign = "-";
1129                                         } else if (el.InnerText ==  "\u200F\u002D") {
1130                                                 // Remove any right-to-left mark characters
1131                                                 ni.NegativeSign = "-";
1132                                         } else
1133                                                 ni.NegativeSign = el.InnerText;                                 
1134                                 }
1135
1136                                 el = node.SelectSingleNode ("infinity");
1137
1138                                 // We cannot use the value from CLDR because many broken
1139                                 // .NET serializers (e.g. JSON) use text value of NegativeInfinity
1140                                 // and different value would break interoperability with .NET
1141                                 var inf = GetInfinitySymbol (ci);
1142                                 if (inf != null)
1143                                         ni.InfinitySymbol = inf;
1144                                 else if (el != null && el.InnerText != "∞") {
1145                                         ni.InfinitySymbol = el.InnerText;
1146                                 }
1147
1148                                 el = node.SelectSingleNode ("perMille");
1149                                 if (el != null)
1150                                         ni.PerMilleSymbol = el.InnerText;
1151
1152                                 el = node.SelectSingleNode ("nan");
1153                                 if (el != null)
1154                                         ni.NaNSymbol = el.InnerText;
1155
1156                                 el = node.SelectSingleNode ("percentSign");
1157                                 if (el != null)
1158                                         ni.PercentSymbol = el.InnerText;
1159
1160                         }
1161
1162                         string value = null;
1163
1164                         // .net has incorrect separators for some countries and we want to be compatible
1165                         switch (ci.Name) {
1166                         case "es-ES":
1167                                 // es-ES does not have group separator but .net has '.'
1168                                 value = ".";
1169                                 break;
1170                         default:
1171                                 if (node != null) {
1172                                         el = node.SelectSingleNode ("group");
1173                                         if (el != null) {
1174                                                 value = el.InnerText;
1175                                         }
1176                                 }
1177
1178                                 break;
1179                         }
1180                                         
1181                         if (value != null) {
1182                                 ni.NumberGroupSeparator =
1183                                 ni.PercentGroupSeparator =
1184                                 ni.CurrencyGroupSeparator = value;
1185                         }
1186                 }
1187
1188                 string GetInfinitySymbol (CultureInfoEntry ci)
1189                 {
1190                         // TODO: Add more
1191                         switch (ci.TwoLetterISOLanguageName) {
1192                                 case "ca":
1193                                         return "Infinit";
1194                                 case "cs":
1195                                 case "sk":
1196                                         return "+nekonečno";
1197                                 case "de":
1198                                         return "+unendlich";
1199                                 case "el":
1200                                         return "Άπειρο";
1201                                 case "es":
1202                                 case "gl":
1203                                         return "Infinito";
1204                                 case "it":
1205                                 case "pt":
1206                                         return "+Infinito";
1207                                 case "nl":
1208                                         return "oneindig";
1209                                 case "fr":
1210                                 case "tzm":
1211                                         return "+Infini";
1212                                 case "pl":
1213                                         return "+nieskończoność";
1214                                 case "ru":
1215                                 case "tg":
1216                                         return "бесконечность";
1217                                 case "sl":
1218                                         return "neskončnost";
1219                                 case "rm":
1220                                         return "+infinit";
1221                                 case "lv":
1222                                         return "bezgalība";
1223                                 case "lt":
1224                                         return "begalybė";
1225                                 case "eu":
1226                                         return "Infinitu";
1227                         }
1228
1229                         return null;
1230                 }
1231
1232                 static string ConvertDatePatternFormat (string format)
1233                 {
1234                         //
1235                         // LDMR uses different characters for some fields
1236                         // http://unicode.org/reports/tr35/#Date_Format_Patterns
1237                         //
1238                         format = format.Replace ("EEEE", "dddd"); // The full name of the day of the week
1239                         format = format.Replace ("LLLL", "MMMM"); // The full month name
1240
1241                         if (format.EndsWith (" y", StringComparison.Ordinal))
1242                                 format += "yyy";
1243
1244                         return format;
1245                 }
1246
1247                 static string ConvertTimePatternFormat (string format)
1248                 {
1249                         format = format.Replace ("a", "tt"); // AM or PM
1250                         return format;
1251                 }
1252
1253                 static void ProcessAllNodes (XmlNodeList list, IList<string> values, Action<IList<string>, string, string> convertor)
1254                 {
1255                         foreach (XmlNode entry in list) {
1256                                 var index = entry.Attributes["type"].Value;
1257                                 var value = entry.InnerText;
1258                                 convertor (values, index, value);
1259                         }
1260                 }
1261
1262                 // All text indexes are 1-based
1263                 static void AddOrReplaceValue (IList<string> list, string oneBasedIndex, string value)
1264                 {
1265                         int index = int.Parse (oneBasedIndex);
1266                         AddOrReplaceValue (list, index - 1, value);
1267                 }
1268
1269                 static readonly string[] day_types = new string[] { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
1270
1271                 static void AddOrReplaceDayValue (IList<string> list, string dayType, string value)
1272                 {
1273                         int index = Array.IndexOf (day_types, dayType);
1274                         AddOrReplaceValue (list, index, value);
1275                 }
1276
1277                 static void AddOrReplaceValue (IList<string> list, int index, string value)
1278                 {
1279                         if (list.Count <= index)
1280                                 ((List<string>) list).AddRange (new string[index - list.Count + 1]);
1281
1282                         list[index] = value;
1283                 }
1284
1285                 sealed class LcidComparer : IComparer<CultureInfoEntry>
1286                 {
1287                         public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1288                         {
1289                                 return x.LCID.CompareTo (y.LCID);
1290                         }
1291                 }
1292
1293                 sealed class ExportNameComparer : IComparer<CultureInfoEntry>
1294                 {
1295                         public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1296                         {
1297                                 return String.Compare (x.GetExportName (), y.GetExportName (), StringComparison.OrdinalIgnoreCase);
1298                         }
1299                 }
1300
1301                 class RegionComparer : IComparer<RegionInfoEntry>
1302                 {
1303                         public int Compare (RegionInfoEntry x, RegionInfoEntry y)
1304                         {
1305                                 return x.TwoLetterISORegionName.CompareTo (y.TwoLetterISORegionName);
1306                         }
1307                 }
1308         }
1309 }