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