Merge pull request #2831 from razzfazz/fix_dllimport
[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}", "PercentNegativePattern", nf.PercentNegativePattern);
178                                 writer.WriteLine ("{0}: {1}", "PercentPositivePattern", nf.PercentPositivePattern);
179                                 writer.WriteLine ("{0}: {1}", "PercentSymbol", nf.PercentSymbol);
180                                 writer.WriteLine ("{0}: {1}", "PerMilleSymbol", nf.PerMilleSymbol);
181                                 writer.WriteLine ("{0}: {1}", "PositiveInfinitySymbol", nf.PositiveInfinitySymbol);
182                                 writer.WriteLine ("{0}: {1}", "PositiveSign", nf.PositiveSign);
183
184                                 if (c.RegionInfoEntry != null) {
185                                         var ri = c.RegionInfoEntry;
186                                         writer.WriteLine ("-- RegionInfo --");
187                                         writer.WriteLine ("{0}: {1}", "CurrencyEnglishName", ri.CurrencyEnglishName);
188                                         writer.WriteLine ("{0}: {1}", "CurrencyNativeName", ri.CurrencyNativeName);
189                                         writer.WriteLine ("{0}: {1}", "CurrencySymbol", ri.CurrencySymbol);
190                                         writer.WriteLine ("{0}: {1}", "DisplayName", ri.DisplayName);
191                                         writer.WriteLine ("{0}: {1}", "EnglishName", ri.EnglishName);
192                                         writer.WriteLine ("{0}: {1}", "GeoId", ri.GeoId);
193                                         writer.WriteLine ("{0}: {1}", "IsMetric", ri.IsMetric);
194                                         writer.WriteLine ("{0}: {1}", "ISOCurrencySymbol", ri.ISOCurrencySymbol);
195                                         writer.WriteLine ("{0}: {1}", "Name", ri.Name);
196                                         writer.WriteLine ("{0}: {1}", "NativeName", ri.NativeName);
197                                         writer.WriteLine ("{0}: {1}", "ThreeLetterISORegionName", ri.ThreeLetterISORegionName);
198                                         writer.WriteLine ("{0}: {1}", "ThreeLetterWindowsRegionName", ri.ThreeLetterWindowsRegionName);
199                                         writer.WriteLine ("{0}: {1}", "TwoLetterISORegionName", ri.TwoLetterISORegionName);
200                                 }
201
202                                 writer.WriteLine ();
203                         }
204                 }
205
206                 static Type GetCalendarType (CalendarType ct)
207                 {
208                         switch (ct) {
209                         case CalendarType.Gregorian:
210                                 return typeof (GregorianCalendar);
211                         case CalendarType.HijriCalendar:
212                                 return typeof (HijriCalendar);
213                         case CalendarType.ThaiBuddhist:
214                                 return typeof (ThaiBuddhistCalendar);
215                         case CalendarType.UmAlQuraCalendar:
216                                 return typeof (UmAlQuraCalendar);
217                         default:
218                                 throw new NotImplementedException ();
219                         }
220                 }
221
222                 static void Dump<T> (TextWriter tw, IList<T> values, string name, bool stopOnNull = false) where T : class
223                 {
224                         tw.Write (name);
225                         tw.Write (": ");
226
227                         for (int i = 0; i < values.Count; ++i) {
228                                 var v = values[i];
229
230                                 if (stopOnNull && v == null)
231                                         break;
232
233                                 if (i > 0)
234                                         tw.Write (", ");
235
236                                 tw.Write (v);
237                         }
238
239                         tw.WriteLine ();
240                 }
241
242                 void Run ()
243                 {
244                         Regex locales_regex = null;
245                         if (Locales != null)
246                                 locales_regex = new Regex (Locales);
247
248                         cultures = new List<CultureInfoEntry> ();
249                         var regions = new List<RegionInfoEntry> ();
250
251
252                         var supplemental = GetXmlDocument (Path.Combine (data_root, "supplemental", "supplementalData.xml"));
253
254                         // Read currencies info
255                         region_currency = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
256                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/currencyData/region")) {
257                                 var child = entry.SelectSingleNode ("currency");
258                                 region_currency.Add (entry.Attributes["iso3166"].Value, child.Attributes["iso4217"].Value);
259                         }
260
261                         // Parent locales
262                         extra_parent_locales = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
263                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/parentLocales/parentLocale")) {
264                                 var parent = entry.Attributes["parent"].Value;
265
266                                 if (parent == "root")
267                                         continue;
268
269                                 var locales = entry.Attributes["locales"].Value;
270                                 foreach (var locale in locales.Split (' '))
271                                         extra_parent_locales.Add (locale, parent);
272                         }
273
274                         var lcdids = GetXmlDocument ("lcids.xml");
275                         foreach (XmlNode lcid in lcdids.SelectNodes ("lcids/lcid")) {
276                                 var name = lcid.Attributes["name"].Value;
277
278                                 if (locales_regex != null && !locales_regex.IsMatch (name))
279                                         continue;
280
281                                 var ci = new CultureInfoEntry ();
282                                 ci.LCID = lcid.Attributes["id"].Value;
283                                 ci.ParentLcid = lcid.Attributes["parent"].Value;
284                                 ci.TwoLetterISOLanguageName = lcid.Attributes["iso2"].Value;
285                                 ci.ThreeLetterISOLanguageName = lcid.Attributes["iso3"].Value;
286                                 ci.ThreeLetterWindowsLanguageName = lcid.Attributes["win"].Value;
287                                 ci.OriginalName = name.Replace ('_', '-');
288                                 ci.TextInfoEntry = new TextInfoEntry ();
289                                 ci.NumberFormatEntry = new NumberFormatEntry ();
290
291                                 if (!Import (ci, name))
292                                         continue;
293
294                                 cultures.Add (ci);
295                         }
296
297                         var doc_english = GetXmlDocument (Path.Combine (data_root, "main", "en.xml"));
298
299                         //
300                         // Fill all EnglishName values from en.xml language file
301                         //
302                         foreach (var ci in cultures) {
303                                 var el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", ci.Language));
304                                 if (el != null)
305                                         ci.EnglishName = el.InnerText;
306
307                                 string s = null;
308                                 if (ci.Script != null) {
309                                         el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/scripts/script[@type='{0}']", ci.Script));
310                                         if (el != null)
311                                                 s = el.InnerText;
312                                 }
313
314                                 if (ci.Territory != null) {
315                                         el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci.Territory));
316                                         if (el != null) {
317                                                 if (s == null)
318                                                         s = el.InnerText;
319                                                 else
320                                                         s = string.Join (", ", s, el.InnerText);
321                                         }
322                                 }
323
324                                 switch (ci.ThreeLetterWindowsLanguageName) {
325                                 case "CHT":
326                                         s = "Traditional";
327                                         break;
328                                 case "CHS":
329                                         s = "Simplified";
330                                         break;
331                                 }
332
333                                 if (s != null)
334                                         ci.EnglishName = string.Format ("{0} ({1})", ci.EnglishName, s);
335
336                                 // Special case legacy chinese
337                                 if (ci.OriginalName == "zh-CHS" || ci.OriginalName == "zh-CHT")
338                                         ci.EnglishName += " Legacy";
339
340                                 // Mono is not localized and supports english only, hence the name will always be same
341                                 ci.DisplayName = ci.EnglishName;
342                         }
343
344                         //
345                         // Fill culture hierarchy for easier data manipulation
346                         //
347                         foreach (var ci in cultures) {
348                                 foreach (var p in cultures.Where (l => ci.LCID == l.ParentLcid)) {
349                                         ci.Children.Add (p);
350                                 }
351                         }
352
353                         currency_fractions = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
354                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/currencyData/fractions/info")) {
355                                 currency_fractions.Add (entry.Attributes["iso4217"].Value, entry.Attributes["digits"].Value);
356                         }
357
358                         var territory2dayofweek = new Dictionary<string, DayOfWeek> (StringComparer.OrdinalIgnoreCase);
359                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/firstDay")) {
360
361                                 if (entry.Attributes ["alt"] != null)
362                                         continue;
363
364                                 DayOfWeek dow;
365                                 switch (entry.Attributes["day"].Value) {
366                                 case "mon":
367                                         dow = DayOfWeek.Monday;
368                                         break;
369                                 case "fri":
370                                         dow = DayOfWeek.Friday;
371                                         break;
372                                 case "sat":
373                                         dow = DayOfWeek.Saturday;
374                                         break;
375                                 case "sun":
376                                         dow = DayOfWeek.Sunday;
377                                         break;
378                                 default:
379                                         throw new NotImplementedException ();
380                                 }
381
382                                 var territories = entry.Attributes["territories"].Value.Split ();
383                                 foreach (var t in territories) {
384                                         territory2dayofweek.Add (t, dow);
385                                 }
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                                 RegionInfoEntry region = regions.Where (l => l.Name == ci.Territory).FirstOrDefault ();
433                                 if (region == null) {
434                                         region = new RegionInfoEntry () {
435                                                 CurrencySymbol = ci.NumberFormatEntry.CurrencySymbol,
436                                                 EnglishName = ci.EnglishName,
437                                                 NativeName = ci.NativeTerritoryName,
438                                                 Name = ci.Territory,
439                                                 TwoLetterISORegionName = ci.Territory,
440                                                 CurrencyNativeName = ci.NativeCurrencyName
441                                         };
442
443                                         var tc = supplemental.SelectSingleNode (string.Format ("supplementalData/codeMappings/territoryCodes[@type='{0}']", ci.Territory));
444                                         region.ThreeLetterISORegionName = tc.Attributes["alpha3"].Value;
445                                         region.ThreeLetterWindowsRegionName = region.ThreeLetterISORegionName;
446
447                                         var el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci.Territory));
448                                         region.EnglishName = el.InnerText;
449                                         region.DisplayName = region.EnglishName;
450
451                                         region.ISOCurrencySymbol = region_currency[ci.Territory];
452
453                                         el = doc_english.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", region.ISOCurrencySymbol));
454                                         region.CurrencyEnglishName = el.InnerText;
455
456                                         if (non_metric.Contains (ci.Territory))
457                                                 region.IsMetric = false;
458
459                                         var lcdid_value = int.Parse (ci.LCID.Substring (2), NumberStyles.HexNumber);
460                                         Patterns.FillValues (lcdid_value, region);
461                                         regions.Add (region);
462                                 }
463
464                                 string fraction_value;
465                                 if (currency_fractions.TryGetValue (region.ISOCurrencySymbol, out fraction_value)) {
466                                         ci.NumberFormatEntry.CurrencyDecimalDigits = fraction_value;
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 "bs":
562                                                 nfe.CurrencySymbol = "KM";
563                                                 break;
564                                         case "es":
565                                         case "fr":
566                                         case "de":
567                                         case "it":
568                                         case "se":
569                                                 nfe.CurrencySymbol = "€";
570                                                 break;
571                                         case "hr":
572                                                 nfe.CurrencySymbol = "kn";
573                                                 break;                          
574                                         case "pt":
575                                                 nfe.CurrencySymbol = "R$";
576                                                 break;
577                                         case "sv":
578                                                 nfe.CurrencySymbol = "kr";
579                                                 break;
580                                         case "ms":
581                                                 nfe.CurrencySymbol = "RM";
582                                                 break;
583                                         case "bn":
584                                                 nfe.CurrencySymbol = "টা";
585                                                 break;
586                                         case "sr-Cyrl":
587                                                 nfe.CurrencySymbol = "Дин.";
588                                                 break;
589                                         case "sr-Latn":
590                                         case "sr":
591                                                 nfe.CurrencySymbol = "Din.";
592                                                 break;
593                                         case "zh":
594                                         case "zh-Hans":
595                                                 nfe.CurrencySymbol = "¥";
596                                                 break;
597                                         case "zh-Hant":
598                                                 nfe.CurrencySymbol = "HK$";
599                                                 break;
600
601                                         default:
602                                                 var all_currencies = new List<string> ();
603                                                 GetAllChildrenValues (ci, all_currencies, l => l.NumberFormatEntry.CurrencySymbol);
604                                                 var children = all_currencies.Where (l => l != null).Distinct ().ToList ();
605
606                                                 if (children.Count == 1) {
607                                                         nfe.CurrencySymbol = children[0];
608                                                 } else if (children.Count == 0) {
609                                                         if (!ci.HasMissingLocale)
610                                                                 Console.WriteLine ("No currency data for `{0}'", ci.Name);
611
612
613                                                 } else {
614                                                         // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
615                                                         // We have to manually disambiguate the correct entry (which is artofficial anyway)
616                                                         throw new ApplicationException (string.Format ("Ambiguous currency data for `{0}'. Possible values '{1}'", ci.Name, string.Join (", ", children)));
617                                                 }
618
619                                                 break;
620                                         }
621                                 }
622
623                                 if (nfe.CurrencyDecimalDigits == null) {
624                                         var all_digits = new List<string> ();
625                                         GetAllChildrenValues (ci, all_digits, l => l.NumberFormatEntry.CurrencyDecimalDigits);
626                                         var children = all_digits.Where (l => l != null).Distinct ().ToList ();
627
628                                         if (children.Count == 1) {
629                                                 nfe.CurrencyDecimalDigits = children[0];
630                                         } else if (children.Count == 0) {
631                                                 if (!ci.HasMissingLocale)
632                                                         Console.WriteLine ("No currency decimal digits data for `{0}'", ci.Name);
633
634                                                 nfe.CurrencyDecimalDigits = "2";
635                                         } else if (ci.IsNeutral) {
636                                                 nfe.CurrencyDecimalDigits = "2";
637                                         } else {
638                                                 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
639                                                 // We have to manually disambiguate the correct entry (which is artofficial anyway)
640                                                 throw new ApplicationException (string.Format ("Ambiguous currency decimal digits data for `{0}'", ci.Name));
641                                         }
642                                 }
643                         }
644
645                         if (OutputCompare)
646                                 Print ();
647
648                         regions.Sort (new RegionComparer ());
649                         for (int i = 0; i < regions.Count; ++i)
650                                 regions[i].Index = i;
651
652                         /**
653                          * Dump each table individually. Using StringBuilders
654                          * because it is easier to debug, should switch to just
655                          * writing to streams eventually.
656                          */
657                         using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
658                                 writer.NewLine = "\n";
659                                 writer.WriteLine ();
660                                 writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
661                                 writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
662                                 writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
663                                 writer.WriteLine ("\n");
664
665                                 writer.WriteLine ("#define NUM_CULTURE_ENTRIES {0}", cultures.Count);
666                                 writer.WriteLine ("#define NUM_REGION_ENTRIES {0}", regions.Count);
667
668                                 writer.WriteLine ("\n");
669
670                                 // Sort the cultures by lcid
671                                 cultures.Sort (new LcidComparer ());
672
673                                 StringBuilder builder = new StringBuilder ();
674                                 int row = 0;
675                                 int count = cultures.Count;
676                                 for (int i = 0; i < count; i++) {
677                                         CultureInfoEntry ci = cultures[i];
678                                         if (ci.DateTimeFormatEntry == null)
679                                                 continue;
680                                         ci.DateTimeFormatEntry.AppendTableRow (builder);
681                                         ci.DateTimeFormatEntry.Row = row++;
682                                         if (i + 1 < count)
683                                                 builder.Append (',');
684                                         builder.Append ('\n');
685                                 }
686
687                                 writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
688                                 writer.Write (builder);
689                                 writer.WriteLine ("};\n\n");
690
691                                 builder = new StringBuilder ();
692                                 row = 0;
693                                 for (int i = 0; i < count; i++) {
694                                         CultureInfoEntry ci = cultures[i];
695                                         if (ci.NumberFormatEntry == null)
696                                                 continue;
697                                         ci.NumberFormatEntry.AppendTableRow (builder);
698                                         ci.NumberFormatEntry.Row = row++;
699                                         if (i + 1 < count)
700                                                 builder.Append (',');
701                                         builder.Append ('\n');
702                                 }
703
704                                 writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
705                                 writer.Write (builder);
706                                 writer.WriteLine ("};\n\n");
707
708                                 builder = new StringBuilder ();
709                                 row = 0;
710                                 for (int i = 0; i < count; i++) {
711                                         CultureInfoEntry ci = cultures[i];
712                                         ci.AppendTableRow (builder);
713                                         ci.Row = row++;
714                                         if (i + 1 < count)
715                                                 builder.Append (',');
716                                         builder.Append ('\n');
717                                 }
718
719                                 writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
720                                 writer.Write (builder);
721                                 writer.WriteLine ("};\n\n");
722
723                                 cultures.Sort (new ExportNameComparer ()); // Sort based on name
724                                 builder = new StringBuilder ();
725                                 for (int i = 0; i < count; i++) {
726                                         CultureInfoEntry ci = cultures[i];
727                                         var name = ci.GetExportName ().ToLowerInvariant ();
728                                         builder.Append ("\t{" + Entry.EncodeStringIdx (name) + ", ");
729                                         builder.Append (ci.Row + "}");
730                                         if (i + 1 < count)
731                                                 builder.Append (',');
732
733                                         builder.AppendFormat ("\t /* {0} */", name);
734                                         builder.Append ('\n');
735                                 }
736
737                                 writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
738                                 writer.Write (builder);
739                                 writer.WriteLine ("};\n\n");
740
741                                 builder = new StringBuilder ();
742                                 int rcount = 0;
743                                 foreach (RegionInfoEntry r in regions) {
744                                         r.AppendTableRow (builder);
745                                         if (++rcount != regions.Count)
746                                                 builder.Append (',');
747
748                                         builder.Append ('\n');
749                                 }
750                                 writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
751                                 writer.Write (builder);
752                                 writer.WriteLine ("};\n\n");
753
754                                 builder = new StringBuilder ();
755                                 rcount = 0;
756                                 foreach (RegionInfoEntry ri in regions) {
757                                         builder.Append ("\t{" + Entry.EncodeStringIdx (ri.TwoLetterISORegionName) + ", ");
758                                         builder.Append (ri.Index + "}");
759                                         if (++rcount != regions.Count)
760                                                 builder.Append (',');
761                                         
762                                         builder.AppendFormat ("\t /* {0} */", ri.TwoLetterISORegionName);
763                                         builder.Append ('\n');
764                                 }
765
766                                 writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
767                                 writer.Write (builder);
768                                 writer.WriteLine ("};\n\n");
769
770                                 writer.WriteLine ("static const char locale_strings [] = {");
771                                 writer.Write (Entry.GetStrings ());
772                                 writer.WriteLine ("};\n\n");
773
774                                 writer.WriteLine ("#endif\n");
775                         }
776                 }
777
778                 static void GetAllChildrenValues<T> (CultureInfoEntry entry, List<T> values, Func<CultureInfoEntry, T> selector)
779                 {
780                         foreach (var e in entry.Children) {
781                                 if (e == entry)
782                                         continue;
783
784                                 values.Add (selector (e));
785
786                                 foreach (var e2 in e.Children) {
787                                         GetAllChildrenValues (e2, values, selector);
788                                 }
789                         }
790                 }
791
792                 static XmlDocument GetXmlDocument (string path)
793                 {
794                         var doc = new XmlDocument ();
795                         doc.Load (new XmlTextReader (path) { /*DtdProcessing = DtdProcessing.Ignore*/ } );
796                         return doc;
797                 }
798
799                 bool Import (CultureInfoEntry data, string locale)
800                 {
801                         string fname = null;
802                         var sep = locale.Split ('_');
803                         data.Language = sep[0];
804
805                         // CLDR strictly follow ISO names, .NET does not
806                         // Replace names where non-iso2 is used, e.g. Norway
807                         if (data.Language != data.TwoLetterISOLanguageName) {
808                                 locale = data.TwoLetterISOLanguageName;
809                                 if (sep.Length > 1) {
810                                         locale += string.Join ("_", sep.Skip (1));
811                                 }
812                         }
813
814                         // Convert broken Chinese names to correct one
815                         switch (locale) {
816                         case "zh_CHS":
817                                 locale = "zh_Hans";
818                                 break;
819                         case "zh_CHT":
820                                 locale = "zh_Hant";
821                                 break;
822                         case "zh_CN":
823                                 locale = "zh_Hans_CN";
824                                 break;
825                         case "zh_HK":
826                                 locale = "zh_Hant_HK";
827                                 break;
828                         case "zh_SG":
829                                 locale = "zh_Hans_SG";
830                                 break;
831                         case "zh_TW":
832                                 locale = "zh_Hant_TW";
833                                 break;
834                         case "zh_MO":
835                                 locale = "zh_Hant_MO";
836                                 break;
837                         }
838
839                         sep = locale.Split ('_');
840
841                         string full_name = Path.Combine (data_root, "main", locale + ".xml");
842                         if (!File.Exists (full_name)) {
843                                 Console.WriteLine ("Missing locale file for `{0}'", locale);
844
845                                 // We could fill default values but that's not as simple as it seems. For instance for non-neutral
846                                 // cultures the next part could be territory or not.
847                                 return false;
848                         } else {
849                                 XmlDocument doc = null;
850
851                                 /*
852                                  * Locale generation is done in several steps, first we
853                                  * read the root file which is the base invariant data
854                                  * then the supplemental root data, 
855                                  * then the language file, the supplemental languages
856                                  * file then the locale file, then the supplemental
857                                  * locale file. Values in each descending file can
858                                  * overwrite previous values.
859                                  */
860                                 foreach (var part in sep) {
861                                         if (fname != null)
862                                                 fname += "_";
863
864                                         fname += part;
865
866                                         XmlDocument xml;
867                                         string extra;
868                                         if (extra_parent_locales.TryGetValue (fname, out extra)) {
869                                                 xml = GetXmlDocument (Path.Combine (data_root, "main", extra + ".xml"));
870                                                 if (doc == null)
871                                                         doc = xml;
872
873                                                 Import (xml, data);
874                                         }
875
876                                         xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
877                                         if (doc == null)
878                                                 doc = xml;
879
880                                         Import (xml, data);
881                                 }
882
883                                 //
884                                 // Extract localized locale name from language xml file. Have to do it after both language and territory are read
885                                 //
886                                 var el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", data.Language));
887                                 if (el != null)
888                                         data.NativeName = el.InnerText;
889
890                                 if (data.Territory != null) {
891                                         el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", data.Territory));
892                                         if (el != null) {
893                                                 // TODO: Should read <localePattern>
894                                                 data.NativeName = string.Format ("{0} ({1})", data.NativeName, el.InnerText);
895                                                 data.NativeTerritoryName = el.InnerText;
896                                         }
897
898                                         string currency;
899                                         // We have territory now we have to run the process again to extract currency symbol
900                                         if (region_currency.TryGetValue (data.Territory, out currency)) {
901                                                 fname = null;
902
903                                                 var xml = GetXmlDocument (Path.Combine (data_root, "main", "root.xml"));
904                                                 el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
905                                                 if (el != null)
906                                                         data.NumberFormatEntry.CurrencySymbol = el.InnerText;
907
908                                                 foreach (var part in sep) {
909                                                         if (fname != null)
910                                                                 fname += "_";
911
912                                                         fname += part;
913
914                                                         xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
915                                                         el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
916                                                         if (el != null)
917                                                                 data.NumberFormatEntry.CurrencySymbol = el.InnerText;
918
919                                                         el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", currency));
920                                                         if (el != null)
921                                                                 data.NativeCurrencyName = el.InnerText;
922                                                 }
923                                         }
924                                 }
925                         }
926
927                         // TODO: Don't have input data available but most values are 2 with few exceptions for 1 and 3
928                         // We don't add 3 as it's for some arabic states only
929                         switch (data.ThreeLetterISOLanguageName) {
930                         case "amh":
931                                 data.NumberFormatEntry.NumberDecimalDigits = 1;
932                                 break;
933                         default:
934                                 data.NumberFormatEntry.NumberDecimalDigits = 2;
935                                 break;
936                         }
937
938                         // TODO: For now we capture only native name for default calendar
939                         data.NativeCalendarNames[((int) data.CalendarType & 0xFF) - 1] = data.DateTimeFormatEntry.NativeCalendarName;
940
941                         var lcdid_value = int.Parse (data.LCID.Substring (2), NumberStyles.HexNumber);
942                         Patterns.FillValues (lcdid_value, data);
943
944                         return true;
945                 }
946
947                 void Import (XmlDocument doc, CultureInfoEntry ci)
948                 {
949                         XmlNodeList nodes;
950                         XmlNode el;
951
952                         //
953                         // Extract script & teritory
954                         //
955                         el = doc.SelectSingleNode ("ldml/identity/script");
956                         if (el != null)
957                                 ci.Script = el.Attributes["type"].Value;
958
959                         el = doc.SelectSingleNode ("ldml/identity/territory");
960                         if (el != null)
961                                 ci.Territory = el.Attributes["type"].Value;
962
963                         var df = ci.DateTimeFormatEntry;
964
965                         string calendar;
966                         // Default calendar is for now always "gregorian"
967                         switch (ci.OriginalName) {
968                         case "th": case "th-TH":
969                                 calendar = "buddhist";
970                                 ci.CalendarType = CalendarType.ThaiBuddhist; // typeof (ThaiBuddhistCalendar);
971                                 break;
972                         case "ar": case "ar-SA":
973                                 calendar = "islamic";
974                                 ci.CalendarType = CalendarType.UmAlQuraCalendar; // typeof (UmAlQuraCalendar);
975                                 break;
976                         case "ps": case "ps-AF": case "prs": case "prs-AF": case "dv": case "dv-MV":
977                                 calendar = "persian";
978                                 ci.CalendarType = CalendarType.HijriCalendar; // typeof (HijriCalendar);
979                                 break;
980                         default:
981                                 calendar = "gregorian";
982                                 ci.CalendarType = CalendarType.Gregorian; // typeof (GregorianCalendar);
983                                 ci.GregorianCalendarType = GregorianCalendarTypes.Localized;
984                                 break;
985                         }
986
987                         var node = doc.SelectSingleNode (string.Format ("ldml/dates/calendars/calendar[@type='{0}']", calendar));
988                         if (node != null) {
989                                 el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/types/type[@type='{0}']", calendar));
990                                 if (el != null)
991                                         df.NativeCalendarName = el.InnerText;
992
993
994                                 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='wide']"/>
995                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
996                                 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
997                                 nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='wide']/month");
998                                 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
999
1000                                 if (df.MonthNames != null) {
1001                                         if (ci.Name == "sv" || ci.Name == "sv-SE") {
1002                                                 ToLower (df.MonthNames);
1003                                         }
1004                                 }
1005
1006                                 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='abbreviated']"/>
1007                                 if (ci.Name == "ja" || ci.Name == "ja-JP") {
1008                                         // Use common number style
1009                                 } else {
1010                                         nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='abbreviated']/month");
1011                                         ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
1012                                         nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='abbreviated']/month");
1013                                         ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
1014                                 }
1015
1016                                 if (df.AbbreviatedMonthNames != null) {
1017                                         if (ci.Name == "sv" || ci.Name == "sv-SE") {
1018                                                 ToLower (df.AbbreviatedMonthNames);
1019                                         }
1020                                 }
1021
1022                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
1023                                 if (nodes != null) {
1024                                         ProcessAllNodes (nodes, df.MonthGenitiveNames, AddOrReplaceValue);
1025                                 }
1026
1027                                 // All values seem to match
1028                                 Array.Copy (df.AbbreviatedMonthNames, df.AbbreviatedMonthGenitiveNames, df.AbbreviatedMonthNames.Length);
1029
1030                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='wide']/day");
1031                                 ProcessAllNodes (nodes, df.DayNames, AddOrReplaceDayValue);
1032
1033                                 // Apply global rule first <alias source="locale" path="../../dayContext[@type='format']/dayWidth[@type='abbreviated']"/>
1034                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='abbreviated']/day");
1035                                 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
1036                                 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='abbreviated']/day");
1037                                 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
1038
1039                                 if (df.AbbreviatedDayNames != null) {
1040                                         if (ci.Name == "sv" || ci.Name == "sv-SE") {
1041                                                 ToLower (df.AbbreviatedDayNames);
1042                                         }
1043                                 }
1044
1045                                 // TODO: This is not really ShortestDayNames as .NET uses it
1046                                 // Apply global rules first <alias source="locale" path="../../dayContext[@type='stand-alone']/dayWidth[@type='narrow']"/>
1047                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='narrow']/day");
1048                                 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1049                                 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='narrow']/day");
1050                                 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1051 /*
1052                                 Cannot really be used it's too different to .NET and most app rely on it
1053  
1054                                 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='full']/dateFormat/pattern");
1055                                 if (el != null)
1056                                         df.LongDatePattern = ConvertDatePatternFormat (el.InnerText);
1057
1058                                 // Medium is our short
1059                                 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='medium']/dateFormat/pattern");
1060                                 if (el != null)
1061                                         df.ShortDatePattern = ConvertDatePatternFormat (el.InnerText);
1062
1063                                 // Medium is our Long
1064                                 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='medium']/timeFormat/pattern");
1065                                 if (el != null)
1066                                         df.LongTimePattern = ConvertTimePatternFormat (el.InnerText);
1067
1068                                 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='short']/timeFormat/pattern");
1069                                 if (el != null)
1070                                         df.ShortTimePattern = ConvertTimePatternFormat (el.InnerText);
1071
1072                                 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='yyyyMMMM']");
1073                                 if (el != null)
1074                                         df.YearMonthPattern = ConvertDatePatternFormat (el.InnerText);
1075
1076                                 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='MMMMdd']");
1077                                 if (el != null)
1078                                         df.MonthDayPattern = ConvertDatePatternFormat (el.InnerText);
1079 */
1080                                 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='am']");
1081                                 if (el == null)
1082                                         // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1083                                         el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='am']");
1084
1085                                 // Manual edits for exact .net compatiblity
1086                                 switch (ci.Name) {
1087                                 case "en-AU":
1088                                         df.AMDesignator = "AM";
1089                                         break;
1090                                 case "en-NZ":
1091                                         df.AMDesignator = "a.m.";
1092                                         break;
1093                                 default:
1094                                         if (el != null)
1095                                                 df.AMDesignator = el.InnerText;
1096                                         break;
1097                                 }
1098
1099                                 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='pm']");
1100                                 if (el == null)
1101                                         // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1102                                         el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='pm']");
1103
1104                                 switch (ci.Name) {
1105                                 case "en-AU":
1106                                         df.PMDesignator = "PM";
1107                                         break;
1108                                 case "en-NZ":
1109                                         df.PMDesignator = "p.m.";
1110                                         break;
1111                                 default:
1112                                         if (el != null)
1113                                                 df.PMDesignator = el.InnerText;
1114                                         break;
1115                                 }
1116                         }
1117
1118                         var ni = ci.NumberFormatEntry;
1119
1120                         node = doc.SelectSingleNode ("ldml/numbers/symbols");
1121                         if (node != null) {
1122                                 el = node.SelectSingleNode ("plusSign");
1123                                 if (el != null)
1124                                         ni.PositiveSign = el.InnerText;
1125
1126                                 el = node.SelectSingleNode ("minusSign");
1127                                 if (el != null) {
1128                                         // CLDR uses unicode negative sign for some culture (e.g sv, is, lt, don't kwnow why) but .net always
1129                                         // uses simple - sign
1130                                         switch (el.InnerText) {
1131                                         case "\u2212":
1132                                         case "\u200F\u002D": // Remove any right-to-left mark characters
1133                                         case "\u200E\u002D":
1134                                                 ni.NegativeSign = "-";
1135                                                 break;
1136                                         default:
1137                                                 ni.NegativeSign = el.InnerText;
1138                                                 break;
1139                                         }
1140                                 }
1141
1142                                 el = node.SelectSingleNode ("infinity");
1143
1144                                 // We cannot use the value from CLDR because many broken
1145                                 // .NET serializers (e.g. JSON) use text value of NegativeInfinity
1146                                 // and different value would break interoperability with .NET
1147                                 var inf = GetInfinitySymbol (ci);
1148                                 if (inf != null)
1149                                         ni.InfinitySymbol = inf;
1150                                 else if (el != null && el.InnerText != "∞") {
1151                                         ni.InfinitySymbol = el.InnerText;
1152                                 }
1153
1154                                 el = node.SelectSingleNode ("perMille");
1155                                 if (el != null)
1156                                         ni.PerMilleSymbol = el.InnerText;
1157
1158                                 el = node.SelectSingleNode ("nan");
1159                                 if (el != null)
1160                                         ni.NaNSymbol = el.InnerText;
1161
1162                                 el = node.SelectSingleNode ("percentSign");
1163                                 if (el != null)
1164                                         ni.PercentSymbol = el.InnerText;
1165
1166                         }
1167
1168                         string value = null;
1169
1170                         // .net has incorrect separators for some countries and we want to be compatible
1171                         switch (ci.Name) {
1172                         case "es-ES":
1173                         case "es":
1174                                 // es-ES does not have group separator but .net has '.'
1175                                 value = ".";
1176                                 break;
1177                         default:
1178                                 if (node != null) {
1179                                         el = node.SelectSingleNode ("group");
1180                                         if (el != null) {
1181                                                 value = el.InnerText;
1182                                         }
1183                                 }
1184
1185                                 break;
1186                         }
1187                                         
1188                         if (value != null) {
1189                                 ni.NumberGroupSeparator = ni.CurrencyGroupSeparator = value;
1190                         }
1191                 }
1192
1193                 static void ToLower (string[] values)
1194                 {
1195                         if (values == null)
1196                                 return;
1197
1198                         for (int i = 0; i < values.Length; ++i) {
1199                                 if (values [i] == null)
1200                                         continue;
1201
1202                                 values [i] = values [i].ToLower ();
1203                         }
1204                 }
1205
1206                 string GetInfinitySymbol (CultureInfoEntry ci)
1207                 {
1208                         // TODO: Add more
1209                         switch (ci.TwoLetterISOLanguageName) {
1210                                 case "ca":
1211                                         return "Infinit";
1212                                 case "cs":
1213                                 case "sk":
1214                                         return "+nekonečno";
1215                                 case "de":
1216                                         return "+unendlich";
1217                                 case "el":
1218                                         return "Άπειρο";
1219                                 case "es":
1220                                 case "gl":
1221                                         return "Infinito";
1222                                 case "it":
1223                                 case "pt":
1224                                         return "+Infinito";
1225                                 case "nl":
1226                                         return "oneindig";
1227                                 case "fr":
1228                                 case "tzm":
1229                                         return "+Infini";
1230                                 case "pl":
1231                                         return "+nieskończoność";
1232                                 case "ru":
1233                                 case "tg":
1234                                         return "бесконечность";
1235                                 case "sl":
1236                                         return "neskončnost";
1237                                 case "rm":
1238                                         return "+infinit";
1239                                 case "lv":
1240                                         return "bezgalība";
1241                                 case "lt":
1242                                         return "begalybė";
1243                                 case "eu":
1244                                         return "Infinitu";
1245                         }
1246
1247                         return null;
1248                 }
1249
1250                 static string ConvertDatePatternFormat (string format)
1251                 {
1252                         //
1253                         // LDMR uses different characters for some fields
1254                         // http://unicode.org/reports/tr35/#Date_Format_Patterns
1255                         //
1256                         format = format.Replace ("EEEE", "dddd"); // The full name of the day of the week
1257                         format = format.Replace ("LLLL", "MMMM"); // The full month name
1258
1259                         if (format.EndsWith (" y", StringComparison.Ordinal))
1260                                 format += "yyy";
1261
1262                         return format;
1263                 }
1264
1265                 static string ConvertTimePatternFormat (string format)
1266                 {
1267                         format = format.Replace ("a", "tt"); // AM or PM
1268                         return format;
1269                 }
1270
1271                 static void ProcessAllNodes (XmlNodeList list, IList<string> values, Action<IList<string>, string, string> convertor)
1272                 {
1273                         foreach (XmlNode entry in list) {
1274                                 var index = entry.Attributes["type"].Value;
1275                                 var value = entry.InnerText;
1276                                 convertor (values, index, value);
1277                         }
1278                 }
1279
1280                 // All text indexes are 1-based
1281                 static void AddOrReplaceValue (IList<string> list, string oneBasedIndex, string value)
1282                 {
1283                         int index = int.Parse (oneBasedIndex);
1284                         AddOrReplaceValue (list, index - 1, value);
1285                 }
1286
1287                 static readonly string[] day_types = new string[] { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
1288
1289                 static void AddOrReplaceDayValue (IList<string> list, string dayType, string value)
1290                 {
1291                         int index = Array.IndexOf (day_types, dayType);
1292                         AddOrReplaceValue (list, index, value);
1293                 }
1294
1295                 static void AddOrReplaceValue (IList<string> list, int index, string value)
1296                 {
1297                         if (list.Count <= index)
1298                                 ((List<string>) list).AddRange (new string[index - list.Count + 1]);
1299
1300                         list[index] = value;
1301                 }
1302
1303                 sealed class LcidComparer : IComparer<CultureInfoEntry>
1304                 {
1305                         public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1306                         {
1307                                 return x.LCID.CompareTo (y.LCID);
1308                         }
1309                 }
1310
1311                 sealed class ExportNameComparer : IComparer<CultureInfoEntry>
1312                 {
1313                         public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1314                         {
1315                                 return String.Compare (x.GetExportName (), y.GetExportName (), StringComparison.OrdinalIgnoreCase);
1316                         }
1317                 }
1318
1319                 class RegionComparer : IComparer<RegionInfoEntry>
1320                 {
1321                         public int Compare (RegionInfoEntry x, RegionInfoEntry y)
1322                         {
1323                                 return x.TwoLetterISORegionName.CompareTo (y.TwoLetterISORegionName);
1324                         }
1325                 }
1326         }
1327 }