New test.
[mono.git] / tools / locale-builder / Driver.cs
1 //
2 // Mono.Tools.LocalBuilder.Driver
3 //
4 // Author(s):
5 //  Jackson Harper (jackson@ximian.com)
6 //  Atsushi Enomoto (atsushi@ximian.com)
7 //
8 // (C) 2004-2005 Novell, Inc (http://www.novell.com)
9 //
10
11
12 using System;
13 using System.IO;
14 using System.Text;
15 using System.Xml;
16 using System.Xml.XPath;
17 using System.Collections;
18 using System.Globalization;
19 using System.Text.RegularExpressions;
20
21 namespace Mono.Tools.LocaleBuilder {
22
23         public class Driver {
24
25                 public static void Main (string [] args)
26                 {
27                         Driver d = new Driver ();
28                         ParseArgs (args, d);
29                         d.Run ();
30                 }
31
32                 private static void ParseArgs (string [] args, Driver d)
33                 {
34                         for (int i = 0; i < args.Length; i++) {
35                                 if (args [i] == "--lang" && i+1 < args.Length)
36                                         d.Lang = args [++i];
37                                 else if (args [i] == "--locales" && i+1 < args.Length)
38                                         d.Locales = args [++i];
39                                 else if (args [i] == "--header" && i + 1 < args.Length)
40                                         d.HeaderFileName = args [++i];
41                         }
42                 }
43
44                 private string lang;
45                 private string locales;
46                 private string header_name;
47                 private ArrayList cultures;
48                 private Hashtable langs;
49                 private Hashtable currency_types;
50                 private Hashtable regions;
51
52                 private XPathDocument lcids_doc;
53
54                 // The lang is the language that display names will be displayed in
55                 public string Lang {
56                         get {
57                                 if (lang == null)
58                                         lang = "en";
59                                 return lang;
60                         }
61                         set { lang = value; }
62                 }
63
64                 public string Locales {
65                         get { return locales; }
66                         set { locales = value; }
67                 }
68
69                 public string HeaderFileName {
70                         get {
71                                 if (header_name == null)
72                                         return "culture-info-tables.h";
73                                 return header_name;
74                         }
75                         set { header_name = value; }
76                 }
77
78                 public void Run ()
79                 {
80                         lcids_doc = GetXPathDocument ("lcids.xml");
81
82                         Regex locales_regex = null;
83                         if (Locales != null)
84                                 locales_regex = new Regex (Locales);
85
86                         langs = new Hashtable ();
87                         cultures = new ArrayList ();
88                         regions = new Hashtable ();
89
90                         LookupRegions ();
91
92                         LookupCurrencyTypes ();
93
94                         foreach (string file in Directory.GetFiles ("locales", "*.xml")) {
95                                 string fn = Path.GetFileNameWithoutExtension (file);
96                                 if (fn == "hy_AM")
97                                         continue; // see bug #75499
98                                 if (locales_regex == null || locales_regex.IsMatch (fn)) {
99                                         ParseLocale (fn);
100                                 }
101                         }
102
103                         /* FIXME: This is hacky.
104                          * Since there is only langs/zh.xml while there are
105                          * two "zh" languages (CHS and CHT), there should be
106                          * different language profiles and we are not likely
107                          * to add lang/* files. So here I just clone zh-CHS
108                          * as zh-CHT
109                          */
110                          foreach (CultureInfoEntry e in cultures) {
111                                 if (e.Name == "zh-CHS") {
112                                         CultureInfoEntry t =
113                                                 CultureInfoEntry.ShallowCopy (e);
114                                         t.Language = "zh-CHT";
115                                         LookupLcids (t, true);
116                                         cultures.Add (t);
117                                         break;
118                                 }
119                          }
120
121                         ArrayList regionList = new ArrayList (regions.Values);
122                         regionList.Sort (RegionComparer.Instance);
123                         int number = 0;
124                         foreach (RegionInfoEntry r in regionList)
125                                 r.RegionId = number++;
126
127                         foreach (CultureInfoEntry e in cultures) {
128                                 int lcid = int.Parse (e.Lcid.Substring (2),
129                                         NumberStyles.HexNumber);
130                                 int idx;
131                                 int start = e.Name.IndexOf ('-') + 1;
132                                 if (start == 0)
133                                         continue;
134                                 for (idx = start; idx < e.Name.Length; idx++)
135                                         if (!Char.IsLetter (e.Name [idx]))
136                                                 break;
137                                 if (start == idx) {
138                                         Console.Error.WriteLine ("Culture {0} {1} is not mappable to Region.", e.Lcid, e.Name);
139                                         continue;
140                                 }
141                                 string name = e.Name.Substring (start, idx - start);
142                                 RegionInfoEntry rm = null;
143                                 foreach (RegionInfoEntry r in regions.Values)
144                                         if (r.ISO2Name == name) {
145                                                 rm = r;
146                                                 break;
147                                         }
148                                 if (rm == null) {
149                                         Console.Error.WriteLine ("No definition for region {0}", name);
150                                         continue;
151                                 }
152                                 e.RegionId = rm.RegionId;
153                         }
154
155                         /**
156                          * Dump each table individually. Using StringBuilders
157                          * because it is easier to debug, should switch to just
158                          * writing to streams eventually.
159                          */
160                         using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
161                                 writer.NewLine = "\n";
162                                 writer.WriteLine ();
163                                 writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
164                                 writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
165                                 writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
166                                 writer.WriteLine ("\n");
167
168                                 writer.WriteLine ("#define NUM_CULTURE_ENTRIES " + cultures.Count);
169                                 writer.WriteLine ("#define NUM_REGION_ENTRIES " + regionList.Count);
170                                 writer.WriteLine ("\n");
171
172                                 // Sort the cultures by lcid
173                                 cultures.Sort (new LcidComparer ());
174
175                                 StringBuilder builder = new StringBuilder ();
176                                 int row = 0;
177                                 int count = cultures.Count;
178                                 for (int i = 0; i < count; i++) {
179                                         CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
180                                         if (ci.DateTimeFormatEntry == null)
181                                                 continue;
182                                         ci.DateTimeFormatEntry.AppendTableRow (builder);
183                                         ci.DateTimeFormatEntry.Row = row++;
184                                         if (i + 1 < count)
185                                                 builder.Append (',');
186                                         builder.Append ('\n');
187                                 }
188
189                                 writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
190                                 writer.Write (builder);
191                                 writer.WriteLine ("};\n\n");
192                                 
193                                 builder = new StringBuilder ();
194                                 row = 0;
195                                 for (int i=0; i < count; i++) {
196                                         CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
197                                         if (ci.NumberFormatEntry == null)
198                                                 continue;
199                                         ci.NumberFormatEntry.AppendTableRow (builder);
200                                         ci.NumberFormatEntry.Row = row++;
201                                         if (i + 1 < count)
202                                                 builder.Append (',');
203                                         builder.Append ('\n');
204                                 }
205
206                                 writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
207                                 writer.Write (builder);
208                                 writer.WriteLine ("};\n\n");
209                                 
210                                 builder = new StringBuilder ();
211                                 row = 0;
212                                 for (int i = 0; i < count; i++) {
213                                         CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
214                                         ci.AppendTableRow (builder);
215                                         ci.Row = row++;
216                                         if (i + 1 < count)
217                                                 builder.Append (',');
218                                         builder.Append ('\n');
219                                 }
220                                 
221                                 writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
222                                 writer.Write (builder);
223                                 writer.WriteLine ("};\n\n");
224
225                                 cultures.Sort (new NameComparer ()); // Sort based on name
226                                 builder = new StringBuilder ();
227                                 for (int i = 0; i < count; i++) {
228                                         CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
229                                         builder.Append ("\t{" + Entry.EncodeStringIdx (ci.Name.ToLower ()) + ", ");
230                                         builder.Append (ci.Row + "}");
231                                         if (i + 1 < count)
232                                                 builder.Append (',');
233                                         builder.Append ('\n');
234                                 }
235
236                                 writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
237                                 writer.Write (builder);
238                                 writer.WriteLine ("};\n\n");
239
240                                 builder = new StringBuilder ();
241                                 int rcount = 0;
242                                 foreach (RegionInfoEntry r in regionList) {
243                                         r.AppendTableRow (builder);
244                                         if (++rcount != regionList.Count)
245                                                 builder.Append (',');
246                                         builder.Append ('\n');
247                                 }
248                                 writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
249                                 writer.Write (builder);
250                                 writer.WriteLine ("};\n\n");
251
252                                 builder = new StringBuilder ();
253                                 rcount = 0;
254                                 foreach (RegionInfoEntry ri in regionList) {
255                                         builder.Append ("\t{" + Entry.EncodeStringIdx (ri.ISO2Name) + ", ");
256                                         builder.Append (ri.RegionId + "}");
257                                         if (++rcount < regionList.Count)
258                                                 builder.Append (',');
259                                         builder.Append ('\n');
260                                 }
261
262                                 writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
263                                 writer.Write (builder);
264                                 writer.WriteLine ("};\n\n");
265
266                                 writer.WriteLine ("static const char locale_strings [] = {");
267                                 writer.Write (Entry.GetStrings ());
268                                 writer.WriteLine ("};\n\n");
269
270                                 writer.WriteLine ("#endif\n");
271                         }
272                 }
273
274                 private XPathDocument GetXPathDocument (string path)
275                 {
276                         XmlTextReader xtr = null;
277                         try {
278                                 xtr = new XmlTextReader (path);
279                                 xtr.XmlResolver = null;
280                                 return new XPathDocument (xtr);
281                         } finally {
282                                 if (xtr != null)
283                                         xtr.Close ();
284                         }
285                 }
286
287                 private string GetShortName (string lang)
288                 {
289                         return lang == "zh-CHS" ? "zh" : lang;
290                 }
291
292                 private bool ParseLang (string lang)
293                 {
294                         XPathDocument doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
295                         XPathNavigator nav = doc.CreateNavigator ();
296                         CultureInfoEntry ci = new CultureInfoEntry ();
297                         string lang_type, terr_type;
298
299 //                        ci.Name = lang; // TODO: might need to be mapped.
300
301                         lang_type = nav.Evaluate ("string (ldml/identity/language/@type)").ToString ();
302                         terr_type = nav.Evaluate ("string (ldml/identity/territory/@type)").ToString ();
303
304                         ci.Language = (lang_type == String.Empty ? null : lang_type);
305                         ci.Territory = (terr_type == String.Empty ? null : terr_type);
306
307                         if (!LookupLcids (ci, true))
308                                 return false;
309
310                         doc = GetXPathDocument (Path.Combine ("langs", GetShortName (Lang) + ".xml"));
311                         nav = doc.CreateNavigator ();
312                         ci.DisplayName = LookupFullName (ci, nav);
313                         
314                         if (Lang == "en") {
315                                 ci.EnglishName = ci.DisplayName;
316                         } else {
317                                 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
318                                 nav = doc.CreateNavigator ();
319                                 ci.EnglishName = LookupFullName (ci, nav);
320                         }
321
322                         if (ci.Language == Lang) {
323                                 ci.NativeName = ci.DisplayName;
324                         } else {
325                                 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
326                                 nav = doc.CreateNavigator ();
327                                 ci.NativeName = LookupFullName (ci, nav);
328                         }
329
330                         // Null these out because langs dont have them
331                         ci.DateTimeFormatEntry = null;
332                         ci.NumberFormatEntry = null;
333
334                         langs [lang] = ci;
335                         cultures.Add (ci);
336
337                         return true;
338                 }
339
340                 private void ParseLocale (string locale)
341                 {
342                         CultureInfoEntry ci;
343
344                         ci = LookupCulture (locale);
345
346                         if (ci == null)
347                                 return;
348
349                         if (langs [GetLanguageFixed (ci)] == null) {
350                                 if (!ParseLang (GetLanguageFixed (ci))) // If we can't parse the lang we cant have the locale
351                                         return;
352                         }
353
354                         cultures.Add (ci);
355                 }
356
357                 private CultureInfoEntry LookupCulture (string locale)
358                 {
359                         string path = Path.Combine ("locales", locale + ".xml");
360                         if (!File.Exists (path))
361                                 return null;
362                         XPathDocument doc = GetXPathDocument (path);
363                         XPathNavigator nav = doc.CreateNavigator ();
364                         CultureInfoEntry ci = new CultureInfoEntry ();
365                         string supp;
366
367 //                        ci.Name = locale; // TODO: Some of these need to be mapped.
368
369                         // First thing we do is get the lang-territory combo, lcid, and full names
370                         ci.Language = nav.Evaluate ("string (ldml/identity/language/@type)").ToString ();
371                         ci.Territory = nav.Evaluate ("string (ldml/identity/territory/@type)").ToString ();
372
373                         if (!LookupLcids (ci, false))
374                                 return null;
375                         LookupNames (ci);
376
377                         /**
378                          * Locale generation is done in six steps, first we
379                          * read the root file which is the base invariant data
380                          * then the supplemental root data, 
381                          * then the language file, the supplemental languages
382                          * file then the locale file, then the supplemental
383                          * locale file. Values in each descending file can
384                          * overwrite previous values.
385                          */
386                         doc = GetXPathDocument (Path.Combine ("langs", "root.xml"));
387                         nav = doc.CreateNavigator ();
388                         Lookup (nav, ci);
389
390                         doc = GetXPathDocument (Path.Combine ("supp", "root.xml"));
391                         nav = doc.CreateNavigator ();
392                         Lookup (nav, ci);
393
394                         doc = GetXPathDocument (Path.Combine ("langs", GetShortName (GetLanguageFixed (ci)) + ".xml"));
395                         nav = doc.CreateNavigator ();
396                         Lookup (nav, ci);
397
398                         supp = Path.Combine ("supp", GetLanguageFixed (ci) + ".xml");
399                         if (File.Exists (supp)) {
400                                 doc = GetXPathDocument (supp);
401                                 nav = doc.CreateNavigator ();
402                                 Lookup (nav, ci);
403                         }
404                         
405                         doc = GetXPathDocument (Path.Combine ("locales", locale + ".xml"));
406                         nav = doc.CreateNavigator ();
407                         Lookup (nav, ci);
408
409                         supp = Path.Combine ("supp", locale + ".xml");
410                         if (File.Exists (supp)) {
411                                 doc = GetXPathDocument (supp);
412                                 nav = doc.CreateNavigator ();
413                                 Lookup (nav, ci);
414                         }
415
416                         return ci;
417                 }
418
419                 private void Lookup (XPathNavigator nav, CultureInfoEntry ci)
420                 {
421                         LookupDateTimeInfo (nav, ci);
422                         LookupNumberInfo (nav, ci);
423                 }
424
425                 private string GetLanguageFixed (CultureInfoEntry ci)
426                 {
427                         // This is a hack, but without it nb-NO and nn-NO won't work.
428                         if (ci.Territory == "NO") {
429                                 switch (ci.Language) {
430                                 case "nb":
431                                 case "nn":
432                                         return "no";
433                                 }
434                         }
435                         return ci.Language;
436                 }
437
438                 private void LookupNames (CultureInfoEntry ci)
439                 {
440                         XPathDocument doc = GetXPathDocument (Path.Combine ("langs", GetShortName (Lang) + ".xml"));
441                         XPathNavigator nav = doc.CreateNavigator ();
442
443                         ci.DisplayName = LookupFullName (ci, nav);
444                         
445                         if (Lang == "en") {
446                                 ci.EnglishName = ci.DisplayName;
447                         } else {
448                                 doc = GetXPathDocument (Path.Combine ("langs", "en.xml"));
449                                 nav = doc.CreateNavigator ();
450                                 ci.EnglishName = LookupFullName (ci, nav);
451                         }
452
453                         if (ci.Language == Lang) {
454                                 ci.NativeName = ci.DisplayName;
455                         } else {
456                                 // FIXME: We use ci.Language here.
457                                 // This is nothing more than hack for nb-NO and nn-NO
458                                 // where Parent of them is nn (not nb or nn).
459                                 string lang = ci.Language;
460                                 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
461                                 nav = doc.CreateNavigator ();
462                                 ci.NativeName = LookupFullName (ci, nav);
463                         }
464                 }
465
466                 private void AddPattern (ArrayList al, string pattern)
467                 {
468                         if (!al.Contains (pattern))
469                                 al.Add (pattern);
470                 }
471
472                 private void LookupDateTimeInfo (XPathNavigator nav, CultureInfoEntry ci)
473                 {
474                         /**
475                          * TODO: Does anyone have multiple calendars?
476                          */
477                         XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("ldml/dates/calendars/calendar");
478
479                         while (ni.MoveNext ()) {
480                                 DateTimeFormatEntry df = ci.DateTimeFormatEntry;
481                                 string cal_type = ni.Current.GetAttribute ("type", String.Empty);
482
483                                 if (cal_type != String.Empty)
484                                         df.CalendarType = cal_type;
485
486                                 XPathNodeIterator ni2 = (XPathNodeIterator) ni.Current.Evaluate ("optionalCalendars/calendar");
487                                 int opt_cal_count = 0;
488                                 while (ni2.MoveNext ()) {
489                                         int type;
490                                         string greg_type_str;
491                                         XPathNavigator df_nav = ni2.Current;
492                                         switch (df_nav.GetAttribute ("type", String.Empty)) {
493                                         case "Gregorian":
494                                                 type = 0;
495                                                 break;
496                                         case "Hijri":
497                                                 type = 0x01;
498                                                 break;
499                                         case "ThaiBuddhist":
500                                                 type = 0x02;
501                                                 break;
502                                         default:
503                                                 Console.WriteLine ("unknown calendar type:  " +
504                                                                 df_nav.GetAttribute ("type", String.Empty));
505                                                 continue;
506                                         }
507                                         type <<= 24;
508                                         greg_type_str = df_nav.GetAttribute ("greg_type", String.Empty);
509                                         if (greg_type_str != null && greg_type_str != String.Empty) {
510                                                 GregorianCalendarTypes greg_type = (GregorianCalendarTypes)
511                                                         Enum.Parse (typeof (GregorianCalendarTypes), greg_type_str);
512                                                 int greg_type_int = (int) greg_type;
513                                                 type |= greg_type_int;
514                                                 
515                                         }
516                                         Console.WriteLine ("Setting cal type: {0:X}  for   {1}", type, ci.Name);
517                                         ci.CalendarData [opt_cal_count++] = type;
518                                 }
519                                 
520                                 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("monthNames/month");
521                                 while (ni2.MoveNext ()) {
522                                         if (ni2.CurrentPosition == 1)
523                                                 df.MonthNames.Clear ();
524                                         df.MonthNames.Add (ni2.Current.Value);
525                                 }
526
527                                 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dayNames/day");
528                                 while (ni2.MoveNext ()) {
529                                         if (ni2.CurrentPosition == 1)
530                                                 df.DayNames.Clear ();
531                                         df.DayNames.Add (ni2.Current.Value);
532                                 }
533
534                                 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dayAbbr/day");
535                                 while (ni2.MoveNext ()) {
536                                         if (ni2.CurrentPosition == 1)
537                                                 df.AbbreviatedDayNames.Clear ();
538                                         df.AbbreviatedDayNames.Add (ni2.Current.Value);
539                                 }
540
541                                 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("monthAbbr/month");
542                                 while (ni2.MoveNext ()) {
543                                         if (ni2.CurrentPosition == 1)
544                                                 df.AbbreviatedMonthNames.Clear ();
545                                         df.AbbreviatedMonthNames.Add (ni2.Current.Value);
546                                 }
547
548                                 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dateFormats/dateFormatLength");
549                                 while (ni2.MoveNext ()) {
550                                         XPathNavigator df_nav = ni2.Current;
551                                         XPathNodeIterator p = df_nav.Select ("dateFormat/pattern");
552                                         string value = null;
553                                         if (p.MoveNext ())
554                                                 value = p.Current.Value;
555                                         XPathNodeIterator ext = null;
556                                         switch (df_nav.GetAttribute ("type", String.Empty)) {
557                                         case "full":
558                                                 if (value != null)
559                                                         ParseFullDateFormat (df, value);
560                                                 break;
561                                         case "long":
562                                                 if (value != null)
563                                                         df.LongDatePattern = value;
564                                                 ext = df_nav.Select ("extraPatterns/pattern");
565                                                 if (ext.MoveNext ()) {
566                                                         df.LongDatePatterns.Clear ();
567                                                         AddPattern (df.LongDatePatterns, df.LongDatePattern);
568                                                         do {
569                                                                 df.LongDatePatterns.Add (ext.Current.Value);
570                                                         } while (ext.MoveNext ());
571                                                 }
572                                                 else
573                                                         AddPattern (df.LongDatePatterns, df.LongDatePattern);
574                                                 break;
575                                         case "short":
576                                                 if (value != null)
577                                                         df.ShortDatePattern = value;
578                                                 ext = df_nav.Select ("extraPatterns/pattern");
579                                                 if (ext.MoveNext ()) {
580                                                         df.ShortDatePatterns.Clear ();
581                                                         AddPattern (df.ShortDatePatterns, df.ShortDatePattern);
582                                                         do {
583                                                                 df.ShortDatePatterns.Add (ext.Current.Value);
584                                                         } while (ext.MoveNext ());
585                                                 }
586                                                 else
587                                                         AddPattern (df.ShortDatePatterns, df.ShortDatePattern);
588                                                 break;
589                                         case "year_month":
590                                                 if (value != null)
591                                                         df.YearMonthPattern = value;
592                                                 break;
593                                         case "month_day":
594                                                 if (value != null)
595                                                         df.MonthDayPattern = value;
596                                                 break;
597                                         }
598                                 }
599
600                                 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("timeFormats/timeFormatLength");
601                                 while (ni2.MoveNext ()) {
602                                         XPathNavigator df_nav = ni2.Current;
603                                         XPathNodeIterator p = df_nav.Select ("timeFormat/pattern");
604                                         string value = null;
605                                         if (p.MoveNext ())
606                                                 value = p.Current.Value;
607                                         XPathNodeIterator ext = null;
608                                         switch (df_nav.GetAttribute ("type", String.Empty)) {
609                                         case "long":
610                                                 if (value != null)
611                                                         df.LongTimePattern = value.Replace ('a', 't');
612                                                 ext = df_nav.Select ("extraPatterns/pattern");
613                                                 if (ext.MoveNext ()) {
614                                                         df.LongTimePatterns.Clear ();
615                                                         AddPattern (df.LongTimePatterns, df.LongTimePattern);
616                                                         do {
617                                                                 df.LongTimePatterns.Add (ext.Current.Value);
618                                                         } while (ext.MoveNext ());
619                                                 }
620                                                 else
621                                                         AddPattern (df.LongTimePatterns, df.LongTimePattern);
622                                                 break;
623                                         case "short":
624                                                 if (value != null)
625                                                         df.ShortTimePattern = value.Replace ('a', 't');
626                                                 ext = df_nav.Select ("extraPatterns/pattern");
627                                                 if (ext.MoveNext ()) {
628                                                         df.ShortTimePatterns.Clear ();
629                                                         AddPattern (df.ShortTimePatterns, df.ShortTimePattern);
630                                                         do {
631                                                                 df.ShortTimePatterns.Add (ext.Current.Value);
632                                                         } while (ext.MoveNext ());
633                                                 }
634                                                 else
635                                                         AddPattern (df.ShortTimePatterns, df.ShortTimePattern);
636                                                 break;
637                                         }
638                                 }
639
640                                 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dateTimeFormats/dateTimeFormatLength/dateTimeFormat/pattern");
641                                 if (ni2.MoveNext ())
642                                         df.FullDateTimePattern = String.Format (ni2.Current.ToString (),
643                                                         df.LongTimePattern, df.LongDatePattern);
644
645                                 XPathNodeIterator am = ni.Current.SelectChildren ("am", "");
646                                 if (am.MoveNext ())
647                                         df.AMDesignator = am.Current.Value;
648                                 XPathNodeIterator pm = ni.Current.SelectChildren ("pm", "");
649                                 if (pm.MoveNext ())
650                                         df.PMDesignator = pm.Current.Value;
651 /*
652                                 string am = (string) ni.Current.Evaluate ("string(am)");
653                                 string pm = (string) ni.Current.Evaluate ("string(pm)");
654
655                                 if (am != String.Empty)
656                                         df.AMDesignator = am;
657                                 if (pm != String.Empty)
658                                         df.PMDesignator = pm;
659 */
660                         }
661
662                         string date_sep = (string) nav.Evaluate ("string(ldml/dates/symbols/dateSeparator)");
663                         string time_sep = (string) nav.Evaluate ("string(ldml/dates/symbols/timeSeparator)");
664
665                         if (date_sep != String.Empty)
666                                 ci.DateTimeFormatEntry.DateSeparator = date_sep;
667                         if (time_sep != String.Empty)
668                                 ci.DateTimeFormatEntry.TimeSeparator = time_sep;
669                 }
670
671                 private void LookupNumberInfo (XPathNavigator nav, CultureInfoEntry ci)
672                 {
673                         XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("ldml/numbers");
674
675                         while (ni.MoveNext ()) {
676                                 LookupNumberSymbols (ni.Current, ci);
677                                 LookupDecimalFormat (ni.Current, ci);
678                                 LookupPercentFormat (ni.Current, ci);
679                                 LookupCurrencyFormat (ni.Current, ci);
680                                 LookupCurrencySymbol (ni.Current, ci);
681                         }
682                 }
683
684                 private void LookupDecimalFormat (XPathNavigator nav, CultureInfoEntry ci)
685                 {
686                         string format = (string) nav.Evaluate ("string(decimalFormats/" +
687                                         "decimalFormatLength/decimalFormat/pattern)");
688
689                         if (format == String.Empty)
690                                 return;
691
692                         string [] part_one, part_two;
693                         string [] pos_neg = format.Split (new char [1] {';'}, 2);
694
695                         // Most of the patterns are common in positive and negative
696                         if (pos_neg.Length == 1)
697                                 pos_neg = new string [] {pos_neg [0], pos_neg [0]};
698
699                         if (pos_neg.Length == 2) {
700                                 
701                                 part_one = pos_neg [0].Split (new char [1] {'.'}, 2);
702                                 if (part_one.Length == 1)
703                                         part_one = new string [] {part_one [0], String.Empty};
704
705                                 if (part_one.Length == 2) {
706                                         // assumed same for both positive and negative
707                                         // decimal digit side
708                                         ci.NumberFormatEntry.NumberDecimalDigits = 0;                                   
709                                         for (int i = 0; i < part_one [1].Length; i++) {
710                                                 if (part_one [1][i] == '#') {
711                                                         ci.NumberFormatEntry.NumberDecimalDigits ++;
712                                                 } else
713                                                         break;                                                          }
714                                         // FIXME: This should be actually done by modifying culture xml files, but too many files to be modified.
715                                         if (ci.NumberFormatEntry.NumberDecimalDigits > 0)
716                                                 ci.NumberFormatEntry.NumberDecimalDigits --;
717
718                                         // decimal grouping side
719                                         part_two = part_one [0].Split (',');
720                                         if (part_two.Length > 1) {
721                                                 int len = part_two.Length - 1;
722                                                 ci.NumberFormatEntry.NumberGroupSizes = new int [len];
723                                                 for (int i = 0; i < len; i++) {
724                                                         string pat = part_two [i + 1];
725                                                         ci.NumberFormatEntry.NumberGroupSizes [i] = pat.Length;
726                                                 }
727                                         } else {
728                                                 ci.NumberFormatEntry.NumberGroupSizes = new int [1] { 3 };
729                                         }
730
731                                         if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith (")")) {
732                                                 ci.NumberFormatEntry.NumberNegativePattern = 0;
733                                         } else if (pos_neg [1].StartsWith ("- ")) {
734                                                 ci.NumberFormatEntry.NumberNegativePattern = 2;
735                                         } else if (pos_neg [1].StartsWith ("-")) {
736                                                 ci.NumberFormatEntry.NumberNegativePattern = 1;
737                                         } else if (pos_neg [1].EndsWith (" -")) {
738                                                 ci.NumberFormatEntry.NumberNegativePattern = 4;
739                                         } else if (pos_neg [1].EndsWith ("-")) {
740                                                 ci.NumberFormatEntry.NumberNegativePattern = 3;
741                                         } else {
742                                                 ci.NumberFormatEntry.NumberNegativePattern = 1;
743                                         }
744                                 }
745                         }
746                 }
747
748                 private void LookupPercentFormat (XPathNavigator nav, CultureInfoEntry ci)
749                 {
750                         string format = (string) nav.Evaluate ("string(percentFormats/" +
751                                         "percentFormatLength/percentFormat/pattern)");
752
753                         if (format == String.Empty)
754                                 return;
755
756                         string [] part_one, part_two;
757
758                         // we don't have percentNegativePattern in CLDR so 
759                         // the percentNegativePattern are just guesses
760                         if (format.StartsWith ("%")) {
761                                 ci.NumberFormatEntry.PercentPositivePattern = 2;
762                                 ci.NumberFormatEntry.PercentNegativePattern = 2;
763                                 format = format.Substring (1);
764                         } else if (format.EndsWith (" %")) {
765                                 ci.NumberFormatEntry.PercentPositivePattern = 0;
766                                 ci.NumberFormatEntry.PercentNegativePattern = 0;
767                                 format = format.Substring (0, format.Length - 2);
768                         } else if (format.EndsWith ("%")) {
769                                 ci.NumberFormatEntry.PercentPositivePattern = 1;
770                                 ci.NumberFormatEntry.PercentNegativePattern = 1;
771                                 format = format.Substring (0, format.Length - 1);
772                         } else {
773                                 ci.NumberFormatEntry.PercentPositivePattern = 0;
774                                 ci.NumberFormatEntry.PercentNegativePattern = 0;
775                         }
776
777                         part_one = format.Split (new char [1] {'.'}, 2);
778                         if (part_one.Length == 2) {
779                                 // assumed same for both positive and negative
780                                 // decimal digit side
781                                 ci.NumberFormatEntry.PercentDecimalDigits = 0;
782                                 for (int i = 0; i < part_one [1].Length; i++) {
783                                         if (part_one [1][i] == '#')
784                                                 ci.NumberFormatEntry.PercentDecimalDigits++;
785                                         else
786                                                 break;
787                                 }
788                         }
789
790                         if (part_one.Length > 0) {
791                                 // percent grouping side
792                                 part_two = part_one [0].Split (',');
793                                 if (part_two.Length > 1) {
794                                         int len = part_two.Length - 1;
795                                         ci.NumberFormatEntry.PercentGroupSizes = new int [len];
796                                         for (int i = 0; i < len; i++) {
797                                                 string pat = part_two [i + 1];
798                                                 if (pat [pat.Length -1] == '0')
799                                                         ci.NumberFormatEntry.PercentDecimalDigits = pat.Length - 1;
800                                                 ci.NumberFormatEntry.PercentGroupSizes [i] = pat.Length;
801                                         }
802                                 } else {
803                                         ci.NumberFormatEntry.PercentGroupSizes = new int [1] { 3 };
804                                         ci.NumberFormatEntry.PercentDecimalDigits = 2;
805                                 }
806                         }
807                 }
808
809                 private void LookupCurrencyFormat (XPathNavigator nav, CultureInfoEntry ci)
810                 {
811                         string format = (string) nav.Evaluate ("string(currencyFormats/" +
812                                         "currencyFormatLength/currencyFormat/pattern)");
813
814                         if (format == String.Empty)
815                                 return;
816
817                         string [] part_one, part_two;
818                         string [] pos_neg = format.Split (new char [1] {';'}, 2);
819         
820                         // Most of the patterns are common in positive and negative
821                         if (pos_neg.Length == 1)
822                                 pos_neg = new string [] {pos_neg [0], pos_neg [0]};
823
824                         if (pos_neg.Length == 2) {
825                                 part_one = pos_neg [0].Split (new char [1] {'.'}, 2);
826                                 if (part_one.Length == 1)
827                                         part_one = new string [] {part_one [0], String.Empty};
828                                 if (part_one.Length == 2) {
829                                         // assumed same for both positive and negative
830                                         // decimal digit side
831                                         ci.NumberFormatEntry.CurrencyDecimalDigits = 0;
832                                         for (int i = 0; i < part_one [1].Length; i++) {
833                                                 if (part_one [1][i] == '0')
834                                                         ci.NumberFormatEntry.CurrencyDecimalDigits++;
835                                                 else
836                                                         break;
837                                         }
838
839                                         // decimal grouping side
840                                         part_two = part_one [0].Split (',');
841                                         if (part_two.Length > 1) {
842                                                 int len = part_two.Length - 1;
843                                                 ci.NumberFormatEntry.CurrencyGroupSizes = new int [len];
844                                                 for (int i = 0; i < len; i++) {
845                                                         string pat = part_two [i + 1];
846                                                         ci.NumberFormatEntry.CurrencyGroupSizes [i] = pat.Length;
847                                                 }
848                                         } else {
849                                                 ci.NumberFormatEntry.CurrencyGroupSizes = new int [1] { 3 };
850                                         }
851
852                                         if (pos_neg [1].StartsWith ("(\u00a4 ") && pos_neg [1].EndsWith (")")) {
853                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 14;
854                                         } else if (pos_neg [1].StartsWith ("(\u00a4") && pos_neg [1].EndsWith (")")) {
855                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 0;
856                                         } else if (pos_neg [1].StartsWith ("\u00a4 ") && pos_neg [1].EndsWith ("-")) {
857                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 11;
858                                         } else if (pos_neg [1].StartsWith ("\u00a4") && pos_neg [1].EndsWith ("-")) {
859                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 3;
860                                         } else if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith (" \u00a4")) {
861                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 15;
862                                         } else if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith ("\u00a4")) {
863                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 4;
864                                         } else if (pos_neg [1].StartsWith ("-") && pos_neg [1].EndsWith (" \u00a4")) {
865                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 8;
866                                         } else if (pos_neg [1].StartsWith ("-") && pos_neg [1].EndsWith ("\u00a4")) {
867                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 5;
868                                         } else if (pos_neg [1].StartsWith ("-\u00a4 ")) {
869                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 9;
870                                         } else if (pos_neg [1].StartsWith ("-\u00a4")) {
871                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 1;
872                                         } else if (pos_neg [1].StartsWith ("\u00a4 -")) {
873                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 12;
874                                         } else if (pos_neg [1].StartsWith ("\u00a4-")) {
875                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 2;
876                                         } else if (pos_neg [1].EndsWith (" \u00a4-")) {
877                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 10;
878                                         } else if (pos_neg [1].EndsWith ("\u00a4-")) {
879                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 7;
880                                         } else if (pos_neg [1].EndsWith ("- \u00a4")) {
881                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 13;
882                                         } else if (pos_neg [1].EndsWith ("-\u00a4")) {
883                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 6;
884                                         } else {
885                                                 ci.NumberFormatEntry.CurrencyNegativePattern = 0;
886                                         }
887                                         
888                                         if (pos_neg [0].StartsWith ("\u00a4 ")) {
889                                                 ci.NumberFormatEntry.CurrencyPositivePattern = 2;
890                                         } else if (pos_neg [0].StartsWith ("\u00a4")) {
891                                                 ci.NumberFormatEntry.CurrencyPositivePattern = 0;
892                                         } else if (pos_neg [0].EndsWith (" \u00a4")) {
893                                                 ci.NumberFormatEntry.CurrencyPositivePattern = 3;
894                                         } else if (pos_neg [0].EndsWith ("\u00a4")) {
895                                                 ci.NumberFormatEntry.CurrencyPositivePattern = 1; 
896                                         } else {
897                                                 ci.NumberFormatEntry.CurrencyPositivePattern = 0;
898                                         }
899                                 }
900                         }
901                 }
902
903                 private void LookupNumberSymbols (XPathNavigator nav, CultureInfoEntry ci)
904                 {
905                         string dec = (string) nav.Evaluate ("string(symbols/decimal)");
906                         string group = (string) nav.Evaluate ("string(symbols/group)");
907                         string percent = (string) nav.Evaluate ("string(symbols/percentSign)");
908                         string positive = (string) nav.Evaluate ("string(symbols/plusSign)");
909                         string negative = (string) nav.Evaluate ("string(symbols/minusSign)");
910                         string per_mille = (string) nav.Evaluate ("string(symbols/perMille)");
911                         string infinity = (string) nav.Evaluate ("string(symbols/infinity)");
912                         string nan = (string) nav.Evaluate ("string(symbols/nan)");
913
914                         if (dec != String.Empty) {
915                                 ci.NumberFormatEntry.NumberDecimalSeparator = dec;
916                                 ci.NumberFormatEntry.PercentDecimalSeparator = dec;
917                                 ci.NumberFormatEntry.CurrencyDecimalSeparator = dec;
918                         }
919
920                         if (group != String.Empty) {
921                                 ci.NumberFormatEntry.NumberGroupSeparator = group;
922                                 ci.NumberFormatEntry.PercentGroupSeparator = group;
923                                 ci.NumberFormatEntry.CurrencyGroupSeparator = group;
924                         }
925
926                         if (percent != String.Empty)
927                                 ci.NumberFormatEntry.PercentSymbol = percent;
928                         if (positive != String.Empty)
929                                 ci.NumberFormatEntry.PositiveSign = positive;
930                         if (negative != String.Empty)
931                                 ci.NumberFormatEntry.NegativeSign = negative;
932                         if (per_mille != String.Empty)
933                                 ci.NumberFormatEntry.PerMilleSymbol = per_mille;
934                         if (infinity != String.Empty)
935                                 ci.NumberFormatEntry.PositiveInfinitySymbol = infinity;
936                         if (nan != String.Empty)
937                                 ci.NumberFormatEntry.NaNSymbol = nan;
938                 }
939
940                 private void LookupCurrencySymbol (XPathNavigator nav, CultureInfoEntry ci)
941                 {
942                         string type = currency_types [ci.Territory] as string;
943
944                         if (type == null) {
945                                 Console.WriteLine ("no currency type for:  " + ci.Territory);
946                                 return;
947                         }
948                         
949                         string cur = (string) nav.Evaluate ("string(currencies/currency [@type='" +
950                                         type + "']/symbol)");
951
952                         if (cur != String.Empty)
953                                 ci.NumberFormatEntry.CurrencySymbol = cur;
954                 }
955
956                 private bool LookupLcids (CultureInfoEntry ci, bool lang)
957                 {
958                         XPathNavigator nav = lcids_doc.CreateNavigator ();
959                         string name = ci.Name;
960                         // Language name does not always consist of locale name.
961                         // (for zh-* it must be either zh-CHS or zh-CHT)
962                         string langName = GetLanguageFixed (ci);
963
964 //                        if (ci.Territory != null)
965 //                                name += "-" + ci.Territory;
966
967                         XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("lcids/lcid[@name='"
968                                         + (lang ? langName : name) + "']");
969                         if (!ni.MoveNext ()) {
970                                 Console.WriteLine ("no lcid found for: {0} ({1}/{2})", name, ci.Language, ci.Territory);
971                                 string file;
972
973                                 if (ci.Territory != null) {
974                                         file = Path.Combine ("locales", ci.Language + "_" + ci.Territory + ".xml");
975                                         Console.WriteLine ("deleting file:  " + file);
976                                         File.Delete (file);
977                                 }
978
979                                 return false;
980                         }
981
982                         string id = ni.Current.GetAttribute ("id", String.Empty);
983                         string parent = ni.Current.GetAttribute ("parent", String.Empty);
984                         string specific = ni.Current.GetAttribute ("specific", String.Empty);
985                         string iso2 = ni.Current.GetAttribute ("iso2", String.Empty);
986                         string iso3 = ni.Current.GetAttribute ("iso3", String.Empty);
987                         string win = ni.Current.GetAttribute ("win", String.Empty);
988                         string icu = ni.Current.GetAttribute ("icu_name", String.Empty);
989
990                         // lcids are in 0x<hex> format
991                         ci.Lcid = id;
992                         ci.ParentLcid = parent;
993                         ci.SpecificLcid = specific;
994                         ci.ISO2Lang = iso2;
995                         ci.ISO3Lang = iso3;
996                         ci.Win3Lang = win;
997                         ci.IcuName = icu;
998                         
999                         ci.TextInfoEntry = new TextInfoEntry (int.Parse (id.Substring (2), NumberStyles.HexNumber), GetXPathDocument ("textinfo.xml"));
1000
1001                         return true;
1002                 }
1003                 
1004                 private string LookupFullName (CultureInfoEntry ci, XPathNavigator nav)
1005                 {
1006                         string pre = "ldml/localeDisplayNames/";
1007                         string ret;
1008
1009                         // FIXME: We use ci.Language here.
1010                         // This is nothing more than hack for nb-NO or nn-NO
1011                         // where Parent of them is nn (not nb or nn).
1012                         ret = (string) nav.Evaluate ("string("+
1013                                         pre + "languages/language[@type='" + GetShortName (ci.Language) + "'])");
1014
1015                         if (ci.Territory == null)
1016                                 return ret;
1017                         ret += " (" + (string) nav.Evaluate ("string("+
1018                                         pre + "territories/territory[@type='" + ci.Territory + "'])") + ")";
1019
1020                         return ret;
1021                 }
1022
1023                 private void LookupRegions ()
1024                 {
1025                         XPathDocument doc = GetXPathDocument ("supplementalData.xml");
1026                         XPathNavigator nav = doc.CreateNavigator ();
1027                         XPathNodeIterator ni = nav.Select ("supplementalData/currencyData/region");
1028                         while (ni.MoveNext ()) {
1029                                 string territory = (string) ni.Current.GetAttribute ("iso3166", String.Empty);
1030                                 string currency = (string) ni.Current.Evaluate ("string(currency/@iso4217)");
1031                                 RegionInfoEntry region = new RegionInfoEntry ();
1032                                 region.ISO2Name = territory.ToUpper ();
1033                                 region.ISOCurrencySymbol = currency;
1034                                 regions [territory] = region;
1035                         }
1036
1037                         doc = GetXPathDocument ("langs/en.xml");
1038                         nav = doc.CreateNavigator ();
1039                         ni = nav.Select ("/ldml/localeDisplayNames/territories/territory");
1040                         while (ni.MoveNext ()) {
1041                                 RegionInfoEntry r = (RegionInfoEntry)
1042                                         regions [ni.Current.GetAttribute ("type", "")];
1043                                 if (r == null)
1044                                         continue;
1045                                 r.EnglishName = ni.Current.Value;
1046                         }
1047
1048                         Hashtable curNames = new Hashtable ();
1049                         ni = nav.Select ("/ldml/numbers/currencies/currency");
1050                         while (ni.MoveNext ())
1051                                 curNames [ni.Current.GetAttribute ("type", "")] =
1052                                         ni.Current.Evaluate ("string (displayName)");
1053
1054                         foreach (RegionInfoEntry r in regions.Values)
1055                                 r.CurrencyEnglishName =
1056                                         (string) curNames [r.ISOCurrencySymbol];
1057                 }
1058
1059                 private void LookupCurrencyTypes ()
1060                 {
1061                         XPathDocument doc = GetXPathDocument ("supplementalData.xml");
1062                         XPathNavigator nav = doc.CreateNavigator ();
1063
1064                         currency_types = new Hashtable ();
1065
1066                         XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("supplementalData/currencyData/region");
1067                         while (ni.MoveNext ()) {
1068                                 string territory = (string) ni.Current.GetAttribute ("iso3166", String.Empty);
1069                                 string currency = (string) ni.Current.Evaluate ("string(currency/@iso4217)");
1070                                 currency_types [territory] = currency;
1071                         }
1072                 }
1073
1074                 static string control_chars = "ghmsftz";
1075
1076                 // HACK: We are trying to build year_month and month_day patterns from the full pattern.
1077                 private void ParseFullDateFormat (DateTimeFormatEntry df, string full)
1078                 {
1079                         
1080                         string month_day = String.Empty;
1081                         string year_month = String.Empty;
1082                         bool in_month_data = false;
1083                         bool in_year_data = false;
1084                         int month_end = 0;
1085                         int year_end = 0;
1086                         bool inquote = false;
1087                         
1088                         for (int i = 0; i < full.Length; i++) {
1089                                 char c = full [i];
1090                                 if (!inquote && c == 'M') {
1091                                         month_day += c;
1092                                         year_month += c;
1093                                         in_year_data = true;
1094                                         in_month_data = true;
1095                                         month_end = month_day.Length;
1096                                         year_end = year_month.Length;
1097                                 } else if (!inquote && Char.ToLower (c) == 'd') {
1098                                         month_day += c;
1099                                         in_month_data = true;
1100                                         in_year_data = false;
1101                                         month_end = month_day.Length;
1102                                 } else if (!inquote && Char.ToLower (c) == 'y') {
1103                                         year_month += c;
1104                                         in_year_data = true;
1105                                         in_month_data = false;
1106                                         year_end = year_month.Length;
1107                                 } else if (!inquote && control_chars.IndexOf (Char.ToLower (c)) >= 0) {
1108                                         in_year_data = false;
1109                                         in_month_data = false;
1110                                 } else if (in_year_data || in_month_data) {
1111                                         if (in_month_data)
1112                                                 month_day += c;
1113                                         if (in_year_data)
1114                                                 year_month += c;
1115                                 }
1116
1117                                 if (c == '\'') {
1118                                         inquote = !inquote;
1119                                 }
1120                         }
1121
1122                         if (month_day != String.Empty) {
1123                                 month_day = month_day.Substring (0, month_end);
1124                                 df.MonthDayPattern = month_day;
1125                         }
1126                         if (year_month != String.Empty) {
1127                                 year_month = year_month.Substring (0, year_end);
1128                                 df.YearMonthPattern = year_month;
1129                         }
1130                 }
1131
1132                 private class LcidComparer : IComparer {
1133
1134                         public int Compare (object a, object b)
1135                         {
1136                                 CultureInfoEntry aa = (CultureInfoEntry) a;
1137                                 CultureInfoEntry bb = (CultureInfoEntry) b;
1138                         
1139                                 return aa.Lcid.CompareTo (bb.Lcid);
1140                         }                
1141                 }
1142
1143                 private class NameComparer : IComparer {
1144
1145                         public int Compare (object a, object b)
1146                         {
1147                                 CultureInfoEntry aa = (CultureInfoEntry) a;
1148                                 CultureInfoEntry bb = (CultureInfoEntry) b;
1149
1150                                 return aa.Name.ToLower ().CompareTo (bb.Name.ToLower ());
1151                         }
1152                 }
1153
1154                 class RegionComparer : IComparer
1155                 {
1156                         public static RegionComparer Instance = new RegionComparer ();
1157                         
1158                         public int Compare (object o1, object o2)
1159                         {
1160                                 RegionInfoEntry r1 = (RegionInfoEntry) o1;
1161                                 RegionInfoEntry r2 = (RegionInfoEntry) o2;
1162                                 return String.CompareOrdinal (
1163                                         r1.ISO2Name, r2.ISO2Name);
1164                         }
1165                 }
1166
1167                 class RegionLCIDMap
1168                 {
1169                         public RegionLCIDMap (int lcid, int regionId)
1170                         {
1171                                 LCID = lcid;
1172                                 RegionId = regionId;
1173                         }
1174
1175                         public int LCID;
1176                         public int RegionId;
1177                 }
1178         }
1179 }
1180
1181