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