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