Merge pull request #347 from JamesB7/master
[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
75                 // The lang is the language that display names will be displayed in
76                 public string Lang
77                 {
78                         get
79                         {
80                                 if (lang == null)
81                                         lang = "en";
82                                 return lang;
83                         }
84                         set { lang = value; }
85                 }
86
87                 public string Locales
88                 {
89                         get { return locales; }
90                         set { locales = value; }
91                 }
92
93                 public string HeaderFileName
94                 {
95                         get
96                         {
97                                 if (header_name == null)
98                                         return "culture-info-tables.h";
99                                 return header_name;
100                         }
101                         set { header_name = value; }
102                 }
103
104                 public bool OutputCompare { get; set; }
105
106                 void Print ()
107                 {
108                         cultures.Sort ((a, b) => int.Parse (a.LCID.Substring (2), NumberStyles.HexNumber).CompareTo (int.Parse (b.LCID.Substring (2), NumberStyles.HexNumber)));
109
110                         var writer = Console.Out;
111
112                         foreach (var c in cultures) {
113                                 writer.WriteLine ("Name: {0}, LCID {1}", c.OriginalName, c.LCID);
114
115                                 writer.WriteLine ("{0}: {1}", "DisplayName", c.DisplayName);
116                                 writer.WriteLine ("{0}: {1}", "EnglishName", c.EnglishName);
117                                 writer.WriteLine ("{0}: {1}", "NativeName", c.NativeName);
118                                 // writer.WriteLine ("{0}: {1}", "OptionalCalendars", c.OptionalCalendars);
119                                 writer.WriteLine ("{0}: {1}", "ThreeLetterISOLanguageName", c.ThreeLetterISOLanguageName);
120                                 writer.WriteLine ("{0}: {1}", "ThreeLetterWindowsLanguageName", c.ThreeLetterWindowsLanguageName);
121                                 writer.WriteLine ("{0}: {1}", "TwoLetterISOLanguageName", c.TwoLetterISOLanguageName);
122                                 writer.WriteLine ("{0}: {1}", "Calendar", GetCalendarType (c.CalendarType));
123
124                                 var df = c.DateTimeFormatEntry;
125                                 writer.WriteLine ("-- DateTimeFormat --");
126                                 Dump (writer, df.AbbreviatedDayNames, "AbbreviatedDayNames");
127                                 Dump (writer, df.AbbreviatedMonthGenitiveNames, "AbbreviatedMonthGenitiveNames");
128                                 Dump (writer, df.AbbreviatedMonthNames, "AbbreviatedMonthNames");
129                                 writer.WriteLine ("{0}: {1}", "AMDesignator", df.AMDesignator);
130                                 writer.WriteLine ("{0}: {1}", "CalendarWeekRule", (CalendarWeekRule) df.CalendarWeekRule);
131                                 writer.WriteLine ("{0}: {1}", "DateSeparator", df.DateSeparator);
132                                 Dump (writer, df.DayNames, "DayNames");
133                                 writer.WriteLine ("{0}: {1}", "FirstDayOfWeek", (DayOfWeek) df.FirstDayOfWeek);
134 //                              Dump (writer, df.GetAllDateTimePatterns (), "GetAllDateTimePatterns");
135                                 writer.WriteLine ("{0}: {1}", "LongDatePattern", df.LongDatePattern);
136                                 writer.WriteLine ("{0}: {1}", "LongTimePattern", df.LongTimePattern);
137                                 writer.WriteLine ("{0}: {1}", "MonthDayPattern", df.MonthDayPattern);
138                                 Dump (writer, df.MonthGenitiveNames, "MonthGenitiveNames");
139                                 Dump (writer, df.MonthNames, "MonthNames");
140                                 writer.WriteLine ("{0}: {1}", "NativeCalendarName", df.NativeCalendarName);
141                                 writer.WriteLine ("{0}: {1}", "PMDesignator", df.PMDesignator);
142                                 writer.WriteLine ("{0}: {1}", "ShortDatePattern", df.ShortDatePattern);
143                                 Dump (writer, df.ShortestDayNames, "ShortestDayNames");
144                                 writer.WriteLine ("{0}: {1}", "ShortTimePattern", df.ShortTimePattern);
145                                 writer.WriteLine ("{0}: {1}", "TimeSeparator", df.TimeSeparator);
146                                 writer.WriteLine ("{0}: {1}", "YearMonthPattern", df.YearMonthPattern);
147
148                                 var ti = c.TextInfoEntry;
149                                 writer.WriteLine ("-- TextInfo --");
150                                 writer.WriteLine ("{0}: {1}", "ANSICodePage", ti.ANSICodePage);
151                                 writer.WriteLine ("{0}: {1}", "EBCDICCodePage", ti.EBCDICCodePage);
152                                 writer.WriteLine ("{0}: {1}", "IsRightToLeft", ti.IsRightToLeft);
153                                 writer.WriteLine ("{0}: {1}", "ListSeparator", ti.ListSeparator);
154                                 writer.WriteLine ("{0}: {1}", "MacCodePage", ti.MacCodePage);
155                                 writer.WriteLine ("{0}: {1}", "OEMCodePage", ti.OEMCodePage);
156
157                                 var nf = c.NumberFormatEntry;
158                                 writer.WriteLine ("-- NumberFormat --");
159                                 writer.WriteLine ("{0}: {1}", "CurrencyDecimalDigits", nf.CurrencyDecimalDigits);
160                                 writer.WriteLine ("{0}: {1}", "CurrencyDecimalSeparator", nf.CurrencyDecimalSeparator);
161                                 writer.WriteLine ("{0}: {1}", "CurrencyGroupSeparator", nf.CurrencyGroupSeparator);
162                                 Dump (writer, nf.CurrencyGroupSizes, "CurrencyGroupSizes", true);
163                                 writer.WriteLine ("{0}: {1}", "CurrencyNegativePattern", nf.CurrencyNegativePattern);
164                                 writer.WriteLine ("{0}: {1}", "CurrencyPositivePattern", nf.CurrencyPositivePattern);
165                                 writer.WriteLine ("{0}: {1}", "CurrencySymbol", nf.CurrencySymbol);
166                                 writer.WriteLine ("{0}: {1}", "DigitSubstitution", nf.DigitSubstitution);
167                                 writer.WriteLine ("{0}: {1}", "NaNSymbol", nf.NaNSymbol);
168                                 Dump (writer, nf.NativeDigits, "NativeDigits");
169                                 writer.WriteLine ("{0}: {1}", "NegativeInfinitySymbol", nf.NegativeInfinitySymbol);
170                                 writer.WriteLine ("{0}: {1}", "NegativeSign", nf.NegativeSign);
171                                 writer.WriteLine ("{0}: {1}", "NumberDecimalDigits", nf.NumberDecimalDigits);
172                                 writer.WriteLine ("{0}: {1}", "NumberDecimalSeparator", nf.NumberDecimalSeparator);
173                                 writer.WriteLine ("{0}: {1}", "NumberGroupSeparator", nf.NumberGroupSeparator);
174                                 Dump (writer, nf.NumberGroupSizes, "NumberGroupSizes", true);
175                                 writer.WriteLine ("{0}: {1}", "NumberNegativePattern", nf.NumberNegativePattern);
176                                 writer.WriteLine ("{0}: {1}", "PercentDecimalDigits", nf.PercentDecimalDigits);
177                                 writer.WriteLine ("{0}: {1}", "PercentDecimalSeparator", nf.PercentDecimalSeparator);
178                                 writer.WriteLine ("{0}: {1}", "PercentGroupSeparator", nf.PercentGroupSeparator);
179                                 Dump (writer, nf.PercentGroupSizes, "PercentGroupSizes", true);
180                                 writer.WriteLine ("{0}: {1}", "PercentNegativePattern", nf.PercentNegativePattern);
181                                 writer.WriteLine ("{0}: {1}", "PercentPositivePattern", nf.PercentPositivePattern);
182                                 writer.WriteLine ("{0}: {1}", "PercentSymbol", nf.PercentSymbol);
183                                 writer.WriteLine ("{0}: {1}", "PerMilleSymbol", nf.PerMilleSymbol);
184                                 writer.WriteLine ("{0}: {1}", "PositiveInfinitySymbol", nf.PositiveInfinitySymbol);
185                                 writer.WriteLine ("{0}: {1}", "PositiveSign", nf.PositiveSign);
186
187                                 if (c.RegionInfoEntry != null) {
188                                         var ri = c.RegionInfoEntry;
189                                         writer.WriteLine ("-- RegionInfo --");
190                                         writer.WriteLine ("{0}: {1}", "CurrencyEnglishName", ri.CurrencyEnglishName);
191                                         writer.WriteLine ("{0}: {1}", "CurrencyNativeName", ri.CurrencyNativeName);
192                                         writer.WriteLine ("{0}: {1}", "CurrencySymbol", ri.CurrencySymbol);
193                                         writer.WriteLine ("{0}: {1}", "DisplayName", ri.DisplayName);
194                                         writer.WriteLine ("{0}: {1}", "EnglishName", ri.EnglishName);
195                                         writer.WriteLine ("{0}: {1}", "GeoId", ri.GeoId);
196                                         writer.WriteLine ("{0}: {1}", "IsMetric", ri.IsMetric);
197                                         writer.WriteLine ("{0}: {1}", "ISOCurrencySymbol", ri.ISOCurrencySymbol);
198                                         writer.WriteLine ("{0}: {1}", "Name", ri.Name);
199                                         writer.WriteLine ("{0}: {1}", "NativeName", ri.NativeName);
200                                         writer.WriteLine ("{0}: {1}", "ThreeLetterISORegionName", ri.ThreeLetterISORegionName);
201                                         writer.WriteLine ("{0}: {1}", "ThreeLetterWindowsRegionName", ri.ThreeLetterWindowsRegionName);
202                                         writer.WriteLine ("{0}: {1}", "TwoLetterISORegionName", ri.TwoLetterISORegionName);
203                                 }
204
205                                 writer.WriteLine ();
206                         }
207                 }
208
209                 static Type GetCalendarType (CalendarType ct)
210                 {
211                         switch (ct) {
212                         case CalendarType.Gregorian:
213                                 return typeof (GregorianCalendar);
214                         case CalendarType.HijriCalendar:
215                                 return typeof (HijriCalendar);
216                         case CalendarType.ThaiBuddhist:
217                                 return typeof (ThaiBuddhistCalendar);
218                         case CalendarType.UmAlQuraCalendar:
219                                 return typeof (UmAlQuraCalendar);
220                         default:
221                                 throw new NotImplementedException ();
222                         }
223                 }
224
225                 static void Dump<T> (TextWriter tw, IList<T> values, string name, bool stopOnNull = false) where T : class
226                 {
227                         tw.Write (name);
228                         tw.Write (": ");
229
230                         for (int i = 0; i < values.Count; ++i) {
231                                 var v = values[i];
232
233                                 if (stopOnNull && v == null)
234                                         break;
235
236                                 if (i > 0)
237                                         tw.Write (", ");
238
239                                 tw.Write (v);
240                         }
241
242                         tw.WriteLine ();
243                 }
244
245                 void Run ()
246                 {
247                         Regex locales_regex = null;
248                         if (Locales != null)
249                                 locales_regex = new Regex (Locales);
250
251                         cultures = new List<CultureInfoEntry> ();
252                         var regions = new List<RegionInfoEntry> ();
253
254
255                         var supplemental = GetXmlDocument (Path.Combine (data_root, "supplemental", "supplementalData.xml"));
256
257                         // Read currencies info
258                         region_currency = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
259                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/currencyData/region")) {
260                                 var child = entry.SelectSingleNode ("currency");
261                                 region_currency.Add (entry.Attributes["iso3166"].Value, child.Attributes["iso4217"].Value);
262                         }
263
264                         var lcdids = GetXmlDocument ("lcids.xml");
265                         foreach (XmlNode lcid in lcdids.SelectNodes ("lcids/lcid")) {
266                                 var name = lcid.Attributes["name"].Value;
267
268                                 if (locales_regex != null && !locales_regex.IsMatch (name))
269                                         continue;
270
271                                 var ci = new CultureInfoEntry ();
272                                 ci.LCID = lcid.Attributes["id"].Value;
273                                 ci.ParentLcid = lcid.Attributes["parent"].Value;
274                                 ci.TwoLetterISOLanguageName = lcid.Attributes["iso2"].Value;
275                                 ci.ThreeLetterISOLanguageName = lcid.Attributes["iso3"].Value;
276                                 ci.ThreeLetterWindowsLanguageName = lcid.Attributes["win"].Value;
277                                 ci.OriginalName = name.Replace ('_', '-');
278                                 ci.TextInfoEntry = new TextInfoEntry ();
279                                 ci.NumberFormatEntry = new NumberFormatEntry ();
280
281                                 if (!Import (ci, name))
282                                         continue;
283
284                                 cultures.Add (ci);
285                         }
286
287                         var doc_english = GetXmlDocument (Path.Combine (data_root, "main", "en.xml"));
288
289                         //
290                         // Fill all EnglishName values from en.xml language file
291                         //
292                         foreach (var ci in cultures) {
293                                 var el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", ci.Language));
294                                 if (el != null)
295                                         ci.EnglishName = el.InnerText;
296
297                                 string s = null;
298                                 if (ci.Script != null) {
299                                         el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/scripts/script[@type='{0}']", ci.Script));
300                                         if (el != null)
301                                                 s = el.InnerText;
302                                 }
303
304                                 if (ci.Territory != null) {
305                                         el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci.Territory));
306                                         if (el != null) {
307                                                 if (s == null)
308                                                         s = el.InnerText;
309                                                 else
310                                                         s = string.Join (", ", s, el.InnerText);
311                                         }
312                                 }
313
314                                 switch (ci.ThreeLetterWindowsLanguageName) {
315                                 case "CHT":
316                                         s = "Traditional";
317                                         break;
318                                 case "CHS":
319                                         s = "Simplified";
320                                         break;
321                                 }
322
323                                 if (s != null)
324                                         ci.EnglishName = string.Format ("{0} ({1})", ci.EnglishName, s);
325
326                                 // Special case legacy chinese
327                                 if (ci.OriginalName == "zh-CHS" || ci.OriginalName == "zh-CHT")
328                                         ci.EnglishName += " Legacy";
329
330                                 // Mono is not localized and supports english only, hence the name will always be same
331                                 ci.DisplayName = ci.EnglishName;
332                         }
333
334                         //
335                         // Fill culture hierarchy for easier data manipulation
336                         //
337                         foreach (var ci in cultures) {
338                                 foreach (var p in cultures.Where (l => ci.LCID == l.ParentLcid)) {
339                                         ci.Children.Add (p);
340                                 }
341                         }
342
343                         currency_fractions = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
344                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/currencyData/fractions/info")) {
345                                 currency_fractions.Add (entry.Attributes["iso4217"].Value, entry.Attributes["digits"].Value);
346                         }
347
348                         var territory2dayofweek = new Dictionary<string, DayOfWeek> (StringComparer.OrdinalIgnoreCase);
349                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/firstDay")) {
350                                 DayOfWeek dow;
351
352                                 switch (entry.Attributes["day"].Value) {
353                                 case "mon":
354                                         dow = DayOfWeek.Monday;
355                                         break;
356                                 case "fri":
357                                         dow = DayOfWeek.Friday;
358                                         break;
359                                 case "sat":
360                                         dow = DayOfWeek.Saturday;
361                                         break;
362                                 case "sun":
363                                         dow = DayOfWeek.Sunday;
364                                         break;
365                                 default:
366                                         throw new NotImplementedException ();
367                                 }
368
369                                 var territories = entry.Attributes["territories"].Value.Split ();
370                                 foreach (var t in territories)
371                                         territory2dayofweek[t] = dow;
372                         }
373
374                         var territory2wr = new Dictionary<string, CalendarWeekRule> (StringComparer.OrdinalIgnoreCase);
375                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/minDays")) {
376                                 CalendarWeekRule rule;
377
378                                 switch (entry.Attributes["count"].InnerText) {
379                                 case "1":
380                                         rule = CalendarWeekRule.FirstDay;
381                                         break;
382                                 case "4":
383                                         rule = CalendarWeekRule.FirstFourDayWeek;
384                                         break;
385                                 default:
386                                         throw new NotImplementedException ();
387                                 }
388
389                                 var territories = entry.Attributes["territories"].InnerText.Split ();
390                                 foreach (var t in territories)
391                                         territory2wr[t] = rule;
392                         }
393
394                         //
395                         // Fill all territory speficic data where territory is available
396                         //
397                         var non_metric = new HashSet<string> ();
398                         foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/measurementData/measurementSystem[@type='US']")) {
399                                 var territories = entry.Attributes["territories"].InnerText.Split ();
400                                 foreach (var t in territories)
401                                         non_metric.Add (t);
402                         }
403
404                         foreach (var ci in cultures) {
405                                 if (ci.Territory == null)
406                                         continue;
407
408                                 DayOfWeek value;
409                                 if (territory2dayofweek.TryGetValue (ci.Territory, out value)) {
410                                         ci.DateTimeFormatEntry.FirstDayOfWeek = (int) value;
411                                 }
412
413                                 CalendarWeekRule rule;
414                                 if (territory2wr.TryGetValue (ci.Territory, out rule)) {
415                                         ci.DateTimeFormatEntry.CalendarWeekRule = (int) rule;
416                                 }
417
418                                 string fraction_value;
419                                 if (currency_fractions.TryGetValue (ci.Territory, out fraction_value)) {
420                                         ci.NumberFormatEntry.CurrencyDecimalDigits = fraction_value;
421                                 }
422
423                                 RegionInfoEntry region = regions.Where (l => l.Name == ci.Territory).FirstOrDefault ();
424                                 if (region == null) {
425                                         region = new RegionInfoEntry () {
426                                                 CurrencySymbol = ci.NumberFormatEntry.CurrencySymbol,
427                                                 EnglishName = ci.EnglishName,
428                                                 NativeName = ci.NativeTerritoryName,
429                                                 Name = ci.Territory,
430                                                 TwoLetterISORegionName = ci.Territory,
431                                                 CurrencyNativeName = ci.NativeCurrencyName
432                                         };
433
434                                         var tc = supplemental.SelectSingleNode (string.Format ("supplementalData/codeMappings/territoryCodes[@type='{0}']", ci.Territory));
435                                         region.ThreeLetterISORegionName = tc.Attributes["alpha3"].Value;
436                                         region.ThreeLetterWindowsRegionName = region.ThreeLetterISORegionName;
437
438                                         var el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci.Territory));
439                                         region.EnglishName = el.InnerText;
440                                         region.DisplayName = region.EnglishName;
441
442                                         region.ISOCurrencySymbol = region_currency[ci.Territory];
443
444                                         el = doc_english.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", region.ISOCurrencySymbol));
445                                         region.CurrencyEnglishName = el.InnerText;
446
447                                         if (non_metric.Contains (ci.Territory))
448                                                 region.IsMetric = false;
449
450                                         var lcdid_value = int.Parse (ci.LCID.Substring (2), NumberStyles.HexNumber);
451                                         Patterns.FillValues (lcdid_value, region);
452                                         regions.Add (region);
453                                 }
454
455                                 ci.RegionInfoEntry = region;
456                         }
457
458                         //
459                         // Fill neutral cultures territory data
460                         //
461                         foreach (var ci in cultures) {
462                                 var dtf = ci.DateTimeFormatEntry;
463                                 if (dtf.FirstDayOfWeek == null) {
464                                         switch (ci.Name) {
465                                         case "ar":
466                                                 dtf.FirstDayOfWeek = (int) DayOfWeek.Saturday;
467                                                 break;
468                                         case "en":
469                                         case "pt":
470                                         case "zh-Hans":
471                                                 dtf.FirstDayOfWeek = (int) DayOfWeek.Sunday;
472                                                 break;
473                                         case "es":
474                                         case "fr":
475                                         case "bn":
476                                         case "sr-Cyrl":
477                                         case "sr-Latn":
478                                                 dtf.FirstDayOfWeek = (int) DayOfWeek.Monday;
479                                                 break;
480                                         default:
481                                                 List<int?> all_fdow = new List<int?> ();
482                                                 GetAllChildrenValues (ci, all_fdow, l => l.DateTimeFormatEntry.FirstDayOfWeek);
483                                                 var children = all_fdow.Where (l => l != null).Distinct ().ToList ();
484
485                                                 if (children.Count == 1) {
486                                                         dtf.FirstDayOfWeek = children[0];
487                                                 } else if (children.Count == 0) {
488                                                         if (!ci.HasMissingLocale)
489                                                                 Console.WriteLine ("No week data for `{0}'", ci.Name);
490
491                                                         // Default to Sunday
492                                                         dtf.FirstDayOfWeek = (int) DayOfWeek.Sunday;
493                                                 } else {
494                                                         // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
495                                                         // We have to manually disambiguate the correct entry (which is artofficial anyway)
496                                                         throw new ApplicationException (string.Format ("Ambiguous week data for `{0}'", ci.Name));
497                                                 }
498
499                                                 break;
500                                         }
501                                 }
502
503                                 if (dtf.CalendarWeekRule == null) {
504                                         switch (ci.Name) {
505                                         case "ar":
506                                         case "en":
507                                         case "es":
508                                         case "zh-Hans":
509                                         case "pt":
510                                         case "fr":
511                                         case "bn":
512                                                 dtf.CalendarWeekRule = (int) CalendarWeekRule.FirstDay;
513                                                 break;
514                                         default:
515                                                 List<int?> all_cwr = new List<int?> ();
516                                                 GetAllChildrenValues (ci, all_cwr, l => l.DateTimeFormatEntry.CalendarWeekRule);
517                                                 var children = all_cwr.Where (l => l != null).Distinct ().ToList ();
518
519                                                 if (children.Count == 1) {
520                                                         dtf.CalendarWeekRule = children[0];
521                                                 } else if (children.Count == 0) {
522                                                         if (!ci.HasMissingLocale)
523                                                                 Console.WriteLine ("No calendar week data for `{0}'", ci.Name);
524
525
526                                                         // Default to FirstDay
527                                                         dtf.CalendarWeekRule = (int) CalendarWeekRule.FirstDay;
528                                                 } else {
529                                                         // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
530                                                         // We have to manually disambiguate the correct entry (which is artofficial anyway)
531                                                         throw new ApplicationException (string.Format ("Ambiguous calendar data for `{0}'", ci.Name));
532                                                 }
533
534                                                 break;
535                                         }
536                                 }
537
538                                 var nfe = ci.NumberFormatEntry;
539                                 if (nfe.CurrencySymbol == null) {
540                                         switch (ci.Name) {
541                                         case "ar":
542                                                 nfe.CurrencySymbol = "ر.س.‏";
543                                                 break;
544                                         case "en":
545                                                 nfe.CurrencySymbol = "$";
546                                                 break;
547                                         case "es":
548                                         case "fr":
549                                                 nfe.CurrencySymbol = "€";
550                                                 break;
551                                         case "pt":
552                                                 nfe.CurrencySymbol = "R$";
553                                                 break;
554                                         case "sv":
555                                                 nfe.CurrencySymbol = "kr";
556                                                 break;
557                                         case "ms":
558                                                 nfe.CurrencySymbol = "RM";
559                                                 break;
560                                         case "bn":
561                                                 nfe.CurrencySymbol = "টা";
562                                                 break;
563                                         case "sr-Cyrl":
564                                                 nfe.CurrencySymbol = "Дин.";
565                                                 break;
566                                         case "sr-Latn":
567                                         case "sr":
568                                                 nfe.CurrencySymbol = "Din.";
569                                                 break;
570                                         case "zh":
571                                                 nfe.CurrencySymbol = "¥";
572                                                 break;
573                                         case "zh-Hant":
574                                                 nfe.CurrencySymbol = "HK$";
575                                                 break;
576                                                 
577                                         default:
578                                                 var all_currencies = new List<string> ();
579                                                 GetAllChildrenValues (ci, all_currencies, l => l.NumberFormatEntry.CurrencySymbol);
580                                                 var children = all_currencies.Where (l => l != null).Distinct ().ToList ();
581
582                                                 if (children.Count == 1) {
583                                                         nfe.CurrencySymbol = children[0];
584                                                 } else if (children.Count == 0) {
585                                                         if (!ci.HasMissingLocale)
586                                                                 Console.WriteLine ("No currency data for `{0}'", ci.Name);
587
588
589                                                 } else {
590                                                         // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
591                                                         // We have to manually disambiguate the correct entry (which is artofficial anyway)
592                                                         throw new ApplicationException (string.Format ("Ambiguous currency data for `{0}'", ci.Name));
593                                                 }
594
595                                                 break;
596                                         }
597                                 }
598
599                                 if (nfe.CurrencyDecimalDigits == null) {
600                                         var all_digits = new List<string> ();
601                                         GetAllChildrenValues (ci, all_digits, l => l.NumberFormatEntry.CurrencyDecimalDigits);
602                                         var children = all_digits.Where (l => l != null).Distinct ().ToList ();
603
604                                         if (children.Count == 1) {
605                                                 nfe.CurrencyDecimalDigits = children[0];
606                                         } else if (children.Count == 0) {
607                                                 if (!ci.HasMissingLocale)
608                                                         Console.WriteLine ("No currency decimal digits data for `{0}'", ci.Name);
609
610                                                 nfe.CurrencyDecimalDigits = "2";
611                                         } else {
612                                                 // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
613                                                 // We have to manually disambiguate the correct entry (which is artofficial anyway)
614                                                 throw new ApplicationException (string.Format ("Ambiguous currency decimal digits data for `{0}'", ci.Name));
615                                         }
616                                 }
617                         }
618
619                         if (OutputCompare)
620                                 Print ();
621
622                         regions.Sort (new RegionComparer ());
623                         for (int i = 0; i < regions.Count; ++i)
624                                 regions[i].Index = i;
625
626                         /**
627                          * Dump each table individually. Using StringBuilders
628                          * because it is easier to debug, should switch to just
629                          * writing to streams eventually.
630                          */
631                         using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
632                                 writer.NewLine = "\n";
633                                 writer.WriteLine ();
634                                 writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
635                                 writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
636                                 writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
637                                 writer.WriteLine ("\n");
638
639                                 writer.WriteLine ("#define NUM_CULTURE_ENTRIES {0}", cultures.Count);
640                                 writer.WriteLine ("#define NUM_REGION_ENTRIES {0}", regions.Count);
641
642                                 writer.WriteLine ("\n");
643
644                                 // Sort the cultures by lcid
645                                 cultures.Sort (new LcidComparer ());
646
647                                 StringBuilder builder = new StringBuilder ();
648                                 int row = 0;
649                                 int count = cultures.Count;
650                                 for (int i = 0; i < count; i++) {
651                                         CultureInfoEntry ci = cultures[i];
652                                         if (ci.DateTimeFormatEntry == null)
653                                                 continue;
654                                         ci.DateTimeFormatEntry.AppendTableRow (builder);
655                                         ci.DateTimeFormatEntry.Row = row++;
656                                         if (i + 1 < count)
657                                                 builder.Append (',');
658                                         builder.Append ('\n');
659                                 }
660
661                                 writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
662                                 writer.Write (builder);
663                                 writer.WriteLine ("};\n\n");
664
665                                 builder = new StringBuilder ();
666                                 row = 0;
667                                 for (int i = 0; i < count; i++) {
668                                         CultureInfoEntry ci = cultures[i];
669                                         if (ci.NumberFormatEntry == null)
670                                                 continue;
671                                         ci.NumberFormatEntry.AppendTableRow (builder);
672                                         ci.NumberFormatEntry.Row = row++;
673                                         if (i + 1 < count)
674                                                 builder.Append (',');
675                                         builder.Append ('\n');
676                                 }
677
678                                 writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
679                                 writer.Write (builder);
680                                 writer.WriteLine ("};\n\n");
681
682                                 builder = new StringBuilder ();
683                                 row = 0;
684                                 for (int i = 0; i < count; i++) {
685                                         CultureInfoEntry ci = cultures[i];
686                                         ci.AppendTableRow (builder);
687                                         ci.Row = row++;
688                                         if (i + 1 < count)
689                                                 builder.Append (',');
690                                         builder.Append ('\n');
691                                 }
692
693                                 writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
694                                 writer.Write (builder);
695                                 writer.WriteLine ("};\n\n");
696
697                                 cultures.Sort (new ExportNameComparer ()); // Sort based on name
698                                 builder = new StringBuilder ();
699                                 for (int i = 0; i < count; i++) {
700                                         CultureInfoEntry ci = cultures[i];
701                                         var name = ci.GetExportName ().ToLowerInvariant ();
702                                         builder.Append ("\t{" + Entry.EncodeStringIdx (name) + ", ");
703                                         builder.Append (ci.Row + "}");
704                                         if (i + 1 < count)
705                                                 builder.Append (',');
706
707                                         builder.AppendFormat ("\t /* {0} */", name);
708                                         builder.Append ('\n');
709                                 }
710
711                                 writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
712                                 writer.Write (builder);
713                                 writer.WriteLine ("};\n\n");
714
715                                 builder = new StringBuilder ();
716                                 int rcount = 0;
717                                 foreach (RegionInfoEntry r in regions) {
718                                         r.AppendTableRow (builder);
719                                         if (++rcount != regions.Count)
720                                                 builder.Append (',');
721
722                                         builder.Append ('\n');
723                                 }
724                                 writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
725                                 writer.Write (builder);
726                                 writer.WriteLine ("};\n\n");
727
728                                 builder = new StringBuilder ();
729                                 rcount = 0;
730                                 foreach (RegionInfoEntry ri in regions) {
731                                         builder.Append ("\t{" + Entry.EncodeStringIdx (ri.TwoLetterISORegionName) + ", ");
732                                         builder.Append (ri.Index + "}");
733                                         if (++rcount != regions.Count)
734                                                 builder.Append (',');
735                                         
736                                         builder.AppendFormat ("\t /* {0} */", ri.TwoLetterISORegionName);
737                                         builder.Append ('\n');
738                                 }
739
740                                 writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
741                                 writer.Write (builder);
742                                 writer.WriteLine ("};\n\n");
743
744                                 writer.WriteLine ("static const char locale_strings [] = {");
745                                 writer.Write (Entry.GetStrings ());
746                                 writer.WriteLine ("};\n\n");
747
748                                 writer.WriteLine ("#endif\n");
749                         }
750                 }
751
752                 static void GetAllChildrenValues<T> (CultureInfoEntry entry, List<T> values, Func<CultureInfoEntry, T> selector)
753                 {
754                         foreach (var e in entry.Children) {
755                                 if (e == entry)
756                                         continue;
757
758                                 values.Add (selector (e));
759
760                                 foreach (var e2 in e.Children) {
761                                         GetAllChildrenValues (e2, values, selector);
762                                 }
763                         }
764                 }
765
766                 static XmlDocument GetXmlDocument (string path)
767                 {
768                         var doc = new XmlDocument ();
769                         doc.Load (new XmlTextReader (path) { /*DtdProcessing = DtdProcessing.Ignore*/ } );
770                         return doc;
771                 }
772
773                 bool Import (CultureInfoEntry data, string locale)
774                 {
775                         string fname = null;
776                         var sep = locale.Split ('_');
777                         data.Language = sep[0];
778
779                         // CLDR strictly follow ISO names, .NET does not
780                         // Replace names where non-iso2 is used, e.g. Norway
781                         if (data.Language != data.TwoLetterISOLanguageName) {
782                                 locale = data.TwoLetterISOLanguageName;
783                                 if (sep.Length > 1) {
784                                         locale += string.Join ("_", sep.Skip (1));
785                                 }
786                         }
787
788                         // Convert broken Chinese names to correct one
789                         switch (locale) {
790                         case "zh_CHS":
791                                 locale = "zh_Hans";
792                                 break;
793                         case "zh_CHT":
794                                 locale = "zh_Hant";
795                                 break;
796                         case "zh_CN":
797                                 locale = "zh_Hans_CN";
798                                 break;
799                         case "zh_HK":
800                                 locale = "zh_Hant_HK";
801                                 break;
802                         case "zh_SG":
803                                 locale = "zh_Hans_SG";
804                                 break;
805                         case "zh_TW":
806                                 locale = "zh_Hant_TW";
807                                 break;
808                         case "zh_MO":
809                                 locale = "zh_Hant_MO";
810                                 break;
811                         }
812
813                         sep = locale.Split ('_');
814
815                         string full_name = Path.Combine (data_root, "main", locale + ".xml");
816                         if (!File.Exists (full_name)) {
817                                 Console.WriteLine ("Missing locale file for `{0}'", locale);
818
819                                 // We could fill default values but that's not as simple as it seems. For instance for non-neutral
820                                 // cultures the next part could be territory or not.
821                                 return false;
822                         } else {
823                                 XmlDocument doc = null;
824
825                                 /*
826                                  * Locale generation is done in several steps, first we
827                                  * read the root file which is the base invariant data
828                                  * then the supplemental root data, 
829                                  * then the language file, the supplemental languages
830                                  * file then the locale file, then the supplemental
831                                  * locale file. Values in each descending file can
832                                  * overwrite previous values.
833                                  */
834                                 foreach (var part in sep) {
835                                         if (fname != null)
836                                                 fname += "_";
837
838                                         fname += part;
839
840                                         var xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
841                                         if (doc == null)
842                                                 doc = xml;
843
844                                         Import (xml, data);
845                                 }
846
847                                 //
848                                 // Extract localized locale name from language xml file. Have to do it after both language and territory are read
849                                 //
850                                 var el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", data.Language));
851                                 if (el != null)
852                                         data.NativeName = el.InnerText;
853
854                                 if (data.Territory != null) {
855                                         el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", data.Territory));
856                                         if (el != null) {
857                                                 // TODO: Should read <localePattern>
858                                                 data.NativeName = string.Format ("{0} ({1})", data.NativeName, el.InnerText);
859                                                 data.NativeTerritoryName = el.InnerText;
860                                         }
861
862                                         string currency;
863                                         // We have territory now we have to run the process again to extract currency symbol
864                                         if (region_currency.TryGetValue (data.Territory, out currency)) {
865                                                 fname = null;
866
867                                                 var xml = GetXmlDocument (Path.Combine (data_root, "main", "root.xml"));
868                                                 el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
869                                                 if (el != null)
870                                                         data.NumberFormatEntry.CurrencySymbol = el.InnerText;
871
872                                                 foreach (var part in sep) {
873                                                         if (fname != null)
874                                                                 fname += "_";
875
876                                                         fname += part;
877
878                                                         xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
879                                                         el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
880                                                         if (el != null)
881                                                                 data.NumberFormatEntry.CurrencySymbol = el.InnerText;
882
883                                                         el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", currency));
884                                                         if (el != null)
885                                                                 data.NativeCurrencyName = el.InnerText;
886                                                 }
887                                         }
888                                 }
889
890                                 if (data.DateTimeFormatEntry.MonthGenitiveNames[0] == null)
891                                         data.DateTimeFormatEntry.MonthGenitiveNames = data.DateTimeFormatEntry.MonthNames;
892
893                                 if (data.DateTimeFormatEntry.AbbreviatedMonthGenitiveNames[0] == null)
894                                         data.DateTimeFormatEntry.AbbreviatedMonthGenitiveNames = data.DateTimeFormatEntry.AbbreviatedMonthNames;
895
896
897                         }
898
899                         // It looks like it never changes
900                         data.DateTimeFormatEntry.TimeSeparator = ":";
901
902                         // TODO: Don't have input data available but most values are 2 with few exceptions for 1 and 3
903                         // We don't add 3 as it's for some arabic states only
904                         switch (data.ThreeLetterISOLanguageName) {
905                         case "amh":
906                                 data.NumberFormatEntry.NumberDecimalDigits =
907                                 data.NumberFormatEntry.PercentDecimalDigits = 1;
908                                 break;
909                         default:
910                                 data.NumberFormatEntry.NumberDecimalDigits =
911                                 data.NumberFormatEntry.PercentDecimalDigits = 2;
912                                 break;
913                         }
914
915                         // TODO: For now we capture only native name for default calendar
916                         data.NativeCalendarNames[((int) data.CalendarType & 0xFF) - 1] = data.DateTimeFormatEntry.NativeCalendarName;
917
918                         var lcdid_value = int.Parse (data.LCID.Substring (2), NumberStyles.HexNumber);
919                         Patterns.FillValues (lcdid_value, data);
920
921                         return true;
922                 }
923
924                 void Import (XmlDocument doc, CultureInfoEntry ci)
925                 {
926                         XmlNodeList nodes;
927                         XmlNode el;
928
929                         //
930                         // Extract script & teritory
931                         //
932                         el = doc.SelectSingleNode ("ldml/identity/script");
933                         if (el != null)
934                                 ci.Script = el.Attributes["type"].Value;
935
936                         el = doc.SelectSingleNode ("ldml/identity/territory");
937                         if (el != null)
938                                 ci.Territory = el.Attributes["type"].Value;
939
940                         var df = ci.DateTimeFormatEntry;
941
942                         string calendar;
943                         // Default calendar is for now always "gregorian"
944                         switch (ci.Name) {
945                         case "th": case "th-TH":
946                                 calendar = "buddhist";
947                                 ci.CalendarType = CalendarType.ThaiBuddhist; // typeof (ThaiBuddhistCalendar);
948                                 break;
949                         case "ar": case "ar-SA":
950                                 calendar = "islamic";
951                                 ci.CalendarType = CalendarType.UmAlQuraCalendar; // typeof (UmAlQuraCalendar);
952                                 break;
953                         case "ps": case "ps-AF": case "prs": case "prs-AF": case "dv": case "dv-MV":
954                                 calendar = "persian";
955                                 ci.CalendarType = CalendarType.HijriCalendar; // typeof (HijriCalendar);
956                                 break;
957                         default:
958                                 calendar = "gregorian";
959                                 ci.CalendarType = CalendarType.Gregorian; // typeof (GregorianCalendar);
960                                 ci.GregorianCalendarType = GregorianCalendarTypes.Localized;
961                                 break;
962                         }
963
964                         var node = doc.SelectSingleNode (string.Format ("ldml/dates/calendars/calendar[@type='{0}']", calendar));
965                         if (node != null) {
966                                 el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/types/type[@type='{0}']", calendar));
967                                 if (el != null)
968                                         df.NativeCalendarName = el.InnerText;
969
970
971                                 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='wide']"/>
972                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
973                                 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
974                                 nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='wide']/month");
975                                 ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
976
977                                 // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='abbreviated']"/>
978                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='abbreviated']/month");
979                                 ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
980                                 nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='abbreviated']/month");
981                                 ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
982
983                                 nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
984                                 if (nodes != null)
985                                         ProcessAllNodes (nodes, df.MonthGenitiveNames, AddOrReplaceValue);
986
987                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='wide']/day");
988                                 ProcessAllNodes (nodes, df.DayNames, AddOrReplaceDayValue);
989
990                                 // Apply global rule first <alias source="locale" path="../../dayContext[@type='format']/dayWidth[@type='abbreviated']"/>
991                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='abbreviated']/day");
992                                 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
993                                 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='abbreviated']/day");
994                                 ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
995
996                                 // TODO: This is not really ShortestDayNames as .NET uses it
997                                 // Apply global rules first <alias source="locale" path="../../dayContext[@type='stand-alone']/dayWidth[@type='narrow']"/>
998                                 nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='narrow']/day");
999                                 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1000                                 nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='narrow']/day");
1001                                 ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
1002 /*
1003                                 Cannot really be used it's too different to .NET and most app rely on it
1004  
1005                                 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='full']/dateFormat/pattern");
1006                                 if (el != null)
1007                                         df.LongDatePattern = ConvertDatePatternFormat (el.InnerText);
1008
1009                                 // Medium is our short
1010                                 el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='medium']/dateFormat/pattern");
1011                                 if (el != null)
1012                                         df.ShortDatePattern = ConvertDatePatternFormat (el.InnerText);
1013
1014                                 // Medium is our Long
1015                                 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='medium']/timeFormat/pattern");
1016                                 if (el != null)
1017                                         df.LongTimePattern = ConvertTimePatternFormat (el.InnerText);
1018
1019                                 el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='short']/timeFormat/pattern");
1020                                 if (el != null)
1021                                         df.ShortTimePattern = ConvertTimePatternFormat (el.InnerText);
1022
1023                                 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='yyyyMMMM']");
1024                                 if (el != null)
1025                                         df.YearMonthPattern = ConvertDatePatternFormat (el.InnerText);
1026
1027                                 el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='MMMMdd']");
1028                                 if (el != null)
1029                                         df.MonthDayPattern = ConvertDatePatternFormat (el.InnerText);
1030 */
1031                                 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='am']");
1032                                 if (el == null)
1033                                         // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1034                                         el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='am']");
1035
1036                                 if (el != null)
1037                                         df.AMDesignator = el.InnerText;
1038
1039                                 el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='pm']");
1040                                 if (el == null)
1041                                         // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
1042                                         el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='pm']");
1043
1044                                 // No data
1045                                 if (el != null)
1046                                         df.PMDesignator = el.InnerText;
1047                         }
1048
1049                         var ni = ci.NumberFormatEntry;
1050
1051                         node = doc.SelectSingleNode ("ldml/numbers/symbols");
1052                         if (node != null) {
1053                                 el = node.SelectSingleNode ("decimal");
1054                                 if (el != null) {
1055                                         ni.NumberDecimalSeparator =
1056                                         ni.PercentDecimalSeparator =
1057                                         ni.CurrencyDecimalSeparator = el.InnerText;
1058                                 }
1059
1060                                 el = node.SelectSingleNode ("plusSign");
1061                                 if (el != null)
1062                                         ni.PositiveSign = el.InnerText;
1063
1064                                 el = node.SelectSingleNode ("minusSign");
1065                                 if (el != null)
1066                                         ni.NegativeSign = el.InnerText;
1067
1068                                 el = node.SelectSingleNode ("infinity");
1069
1070                                 // We cannot use the value from CLDR because many broken
1071                                 // .NET serializers (e.g. JSON) use text value of NegativeInfinity
1072                                 // and different value would break interoperability with .NET
1073                                 if (el != null && el.InnerText != "∞") {
1074                                         ni.InfinitySymbol = el.InnerText;
1075                                 }
1076
1077                                 el = node.SelectSingleNode ("perMille");
1078                                 if (el != null)
1079                                         ni.PerMilleSymbol = el.InnerText;
1080
1081                                 el = node.SelectSingleNode ("nan");
1082                                 if (el != null)
1083                                         ni.NaNSymbol = el.InnerText;
1084
1085                                 el = node.SelectSingleNode ("percentSign");
1086                                 if (el != null)
1087                                         ni.PercentSymbol = el.InnerText;
1088
1089                                 el = node.SelectSingleNode ("group");
1090                                 if (el != null) {
1091                                         ni.NumberGroupSeparator =
1092                                         ni.PercentGroupSeparator =
1093                                         ni.CurrencyGroupSeparator = el.InnerText;
1094                                 }
1095                         }
1096                 }
1097
1098                 static string ConvertDatePatternFormat (string format)
1099                 {
1100                         //
1101                         // LDMR uses different characters for some fields
1102                         // http://unicode.org/reports/tr35/#Date_Format_Patterns
1103                         //
1104                         format = format.Replace ("EEEE", "dddd"); // The full name of the day of the week
1105                         format = format.Replace ("LLLL", "MMMM"); // The full month name
1106
1107                         if (format.EndsWith (" y", StringComparison.Ordinal))
1108                                 format += "yyy";
1109
1110                         return format;
1111                 }
1112
1113                 static string ConvertTimePatternFormat (string format)
1114                 {
1115                         format = format.Replace ("a", "tt"); // AM or PM
1116                         return format;
1117                 }
1118
1119                 static void ProcessAllNodes (XmlNodeList list, IList<string> values, Action<IList<string>, string, string> convertor)
1120                 {
1121                         foreach (XmlNode entry in list) {
1122                                 var index = entry.Attributes["type"].Value;
1123                                 var value = entry.InnerText;
1124                                 convertor (values, index, value);
1125                         }
1126                 }
1127
1128                 // All text indexes are 1-based
1129                 static void AddOrReplaceValue (IList<string> list, string oneBasedIndex, string value)
1130                 {
1131                         int index = int.Parse (oneBasedIndex);
1132                         AddOrReplaceValue (list, index - 1, value);
1133                 }
1134
1135                 static readonly string[] day_types = new string[] { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
1136
1137                 static void AddOrReplaceDayValue (IList<string> list, string dayType, string value)
1138                 {
1139                         int index = Array.IndexOf (day_types, dayType);
1140                         AddOrReplaceValue (list, index, value);
1141                 }
1142
1143                 static void AddOrReplaceValue (IList<string> list, int index, string value)
1144                 {
1145                         if (list.Count <= index)
1146                                 ((List<string>) list).AddRange (new string[index - list.Count + 1]);
1147
1148                         list[index] = value;
1149                 }
1150
1151                 sealed class LcidComparer : IComparer<CultureInfoEntry>
1152                 {
1153                         public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1154                         {
1155                                 return x.LCID.CompareTo (y.LCID);
1156                         }
1157                 }
1158
1159                 sealed class ExportNameComparer : IComparer<CultureInfoEntry>
1160                 {
1161                         public int Compare (CultureInfoEntry x, CultureInfoEntry y)
1162                         {
1163                                 return String.Compare (x.GetExportName (), y.GetExportName (), StringComparison.OrdinalIgnoreCase);
1164                         }
1165                 }
1166
1167                 class RegionComparer : IComparer<RegionInfoEntry>
1168                 {
1169                         public int Compare (RegionInfoEntry x, RegionInfoEntry y)
1170                         {
1171                                 return x.TwoLetterISORegionName.CompareTo (y.TwoLetterISORegionName);
1172                         }
1173                 }
1174         }
1175 }