it's valid to pass NULLs to g_hash_table_lookup_extended. don't crash if the user...
[mono-project/dkf.git] / tools / locale-builder / Driver.cs
blob7bd2520421f0d294a0a8dc49a9311f1ab17e935e
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 //
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;
21 namespace Mono.Tools.LocaleBuilder {
23 public class Driver {
25 public static void Main (string [] args)
27 Driver d = new Driver ();
28 ParseArgs (args, d);
29 d.Run ();
32 private static void ParseArgs (string [] args, Driver d)
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];
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;
52 private XPathDocument lcids_doc;
54 // The lang is the language that display names will be displayed in
55 public string Lang {
56 get {
57 if (lang == null)
58 lang = "en";
59 return lang;
61 set { lang = value; }
64 public string Locales {
65 get { return locales; }
66 set { locales = value; }
69 public string HeaderFileName {
70 get {
71 if (header_name == null)
72 return "culture-info-tables.h";
73 return header_name;
75 set { header_name = value; }
78 public void Run ()
80 lcids_doc = GetXPathDocument ("lcids.xml");
82 Regex locales_regex = null;
83 if (Locales != null)
84 locales_regex = new Regex (Locales);
86 langs = new Hashtable ();
87 cultures = new ArrayList ();
88 regions = new Hashtable ();
90 LookupRegions ();
92 LookupCurrencyTypes ();
94 foreach (string file in Directory.GetFiles ("locales", "*.xml")) {
95 string fn = Path.GetFileNameWithoutExtension (file);
96 if (fn == "hy_AM")
97 continue; // see bug #75499
98 if (locales_regex == null || locales_regex.IsMatch (fn)) {
99 ParseLocale (fn);
103 /* FIXME: This is hacky.
104 * Since there is only langs/zh.xml while there are
105 * two "zh" languages (CHS and CHT), there should be
106 * different language profiles and we are not likely
107 * to add lang/* files. So here I just clone zh-CHS
108 * as zh-CHT
110 foreach (CultureInfoEntry e in cultures) {
111 if (e.Name == "zh-CHS") {
112 CultureInfoEntry t =
113 CultureInfoEntry.ShallowCopy (e);
114 t.Language = "zh-CHT";
115 LookupLcids (t, true);
116 cultures.Add (t);
117 break;
121 ArrayList regionList = new ArrayList (regions.Values);
122 regionList.Sort (RegionComparer.Instance);
123 int number = 0;
124 foreach (RegionInfoEntry r in regionList)
125 r.RegionId = number++;
127 foreach (CultureInfoEntry e in cultures) {
128 int lcid = int.Parse (e.Lcid.Substring (2),
129 NumberStyles.HexNumber);
130 int idx;
131 int start = e.Name.IndexOf ('-') + 1;
132 if (start == 0)
133 continue;
134 for (idx = start; idx < e.Name.Length; idx++)
135 if (!Char.IsLetter (e.Name [idx]))
136 break;
137 if (start == idx) {
138 Console.Error.WriteLine ("Culture {0} {1} is not mappable to Region.", e.Lcid, e.Name);
139 continue;
141 string name = e.Name.Substring (start, idx - start);
142 RegionInfoEntry rm = null;
143 foreach (RegionInfoEntry r in regions.Values)
144 if (r.ISO2Name == name) {
145 rm = r;
146 break;
148 if (rm == null) {
149 Console.Error.WriteLine ("No definition for region {0}", name);
150 continue;
152 e.RegionId = rm.RegionId;
156 * Dump each table individually. Using StringBuilders
157 * because it is easier to debug, should switch to just
158 * writing to streams eventually.
160 using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
161 writer.NewLine = "\n";
162 writer.WriteLine ();
163 writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
164 writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
165 writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
166 writer.WriteLine ("\n");
168 writer.WriteLine ("#define NUM_CULTURE_ENTRIES " + cultures.Count);
169 writer.WriteLine ("#define NUM_REGION_ENTRIES " + regionList.Count);
170 writer.WriteLine ("\n");
172 // Sort the cultures by lcid
173 cultures.Sort (new LcidComparer ());
175 StringBuilder builder = new StringBuilder ();
176 int row = 0;
177 int count = cultures.Count;
178 for (int i = 0; i < count; i++) {
179 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
180 if (ci.DateTimeFormatEntry == null)
181 continue;
182 ci.DateTimeFormatEntry.AppendTableRow (builder);
183 ci.DateTimeFormatEntry.Row = row++;
184 if (i + 1 < count)
185 builder.Append (',');
186 builder.Append ('\n');
189 writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
190 writer.Write (builder);
191 writer.WriteLine ("};\n\n");
193 builder = new StringBuilder ();
194 row = 0;
195 for (int i=0; i < count; i++) {
196 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
197 if (ci.NumberFormatEntry == null)
198 continue;
199 ci.NumberFormatEntry.AppendTableRow (builder);
200 ci.NumberFormatEntry.Row = row++;
201 if (i + 1 < count)
202 builder.Append (',');
203 builder.Append ('\n');
206 writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
207 writer.Write (builder);
208 writer.WriteLine ("};\n\n");
210 builder = new StringBuilder ();
211 row = 0;
212 for (int i = 0; i < count; i++) {
213 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
214 ci.AppendTableRow (builder);
215 ci.Row = row++;
216 if (i + 1 < count)
217 builder.Append (',');
218 builder.Append ('\n');
221 writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
222 writer.Write (builder);
223 writer.WriteLine ("};\n\n");
225 cultures.Sort (new NameComparer ()); // Sort based on name
226 builder = new StringBuilder ();
227 for (int i = 0; i < count; i++) {
228 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
229 builder.Append ("\t{" + Entry.EncodeStringIdx (ci.Name.ToLower ()) + ", ");
230 builder.Append (ci.Row + "}");
231 if (i + 1 < count)
232 builder.Append (',');
233 builder.Append ('\n');
236 writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
237 writer.Write (builder);
238 writer.WriteLine ("};\n\n");
240 builder = new StringBuilder ();
241 int rcount = 0;
242 foreach (RegionInfoEntry r in regionList) {
243 r.AppendTableRow (builder);
244 if (++rcount != regionList.Count)
245 builder.Append (',');
246 builder.Append ('\n');
248 writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
249 writer.Write (builder);
250 writer.WriteLine ("};\n\n");
252 builder = new StringBuilder ();
253 rcount = 0;
254 foreach (RegionInfoEntry ri in regionList) {
255 builder.Append ("\t{" + Entry.EncodeStringIdx (ri.ISO2Name) + ", ");
256 builder.Append (ri.RegionId + "}");
257 if (++rcount < regionList.Count)
258 builder.Append (',');
259 builder.Append ('\n');
262 writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
263 writer.Write (builder);
264 writer.WriteLine ("};\n\n");
266 writer.WriteLine ("static const char locale_strings [] = {");
267 writer.Write (Entry.GetStrings ());
268 writer.WriteLine ("};\n\n");
270 writer.WriteLine ("#endif\n");
274 private XPathDocument GetXPathDocument (string path)
276 XmlTextReader xtr = null;
277 try {
278 xtr = new XmlTextReader (path);
279 xtr.XmlResolver = null;
280 return new XPathDocument (xtr);
281 } finally {
282 if (xtr != null)
283 xtr.Close ();
287 private string GetShortName (string lang)
289 return lang == "zh-CHS" ? "zh" : lang;
292 private bool ParseLang (string lang)
294 XPathDocument doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
295 XPathNavigator nav = doc.CreateNavigator ();
296 CultureInfoEntry ci = LookupCulture (GetShortName (lang), true);
297 string lang_type, terr_type;
299 // ci.Name = lang; // TODO: might need to be mapped.
301 lang_type = nav.Evaluate ("string (ldml/identity/language/@type)").ToString ();
302 terr_type = nav.Evaluate ("string (ldml/identity/territory/@type)").ToString ();
304 ci.Language = (lang_type == String.Empty ? null : lang_type);
305 ci.Territory = (terr_type == String.Empty ? null : terr_type);
307 if (!LookupLcids (ci, true))
308 return false;
310 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (Lang) + ".xml"));
311 nav = doc.CreateNavigator ();
312 ci.DisplayName = LookupFullName (ci, nav);
314 if (Lang == "en") {
315 ci.EnglishName = ci.DisplayName;
316 } else {
317 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
318 nav = doc.CreateNavigator ();
319 ci.EnglishName = LookupFullName (ci, nav);
322 if (ci.Language == Lang) {
323 ci.NativeName = ci.DisplayName;
324 } else {
325 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
326 nav = doc.CreateNavigator ();
327 ci.NativeName = LookupFullName (ci, nav);
330 langs [lang] = ci;
331 cultures.Add (ci);
333 return true;
336 private void ParseLocale (string locale)
338 CultureInfoEntry ci;
340 ci = LookupCulture (locale);
342 if (ci == null)
343 return;
345 if (langs [GetLanguageFixed (ci)] == null) {
346 if (!ParseLang (GetLanguageFixed (ci))) // If we can't parse the lang we cant have the locale
347 return;
350 cultures.Add (ci);
353 private CultureInfoEntry LookupCulture (string locale)
355 return LookupCulture (locale, false);
357 private CultureInfoEntry LookupCulture (string locale, bool is_language)
359 string path = Path.Combine (is_language ? "langs" : "locales", locale + ".xml");
360 if (!File.Exists (path))
361 return null;
362 XPathDocument doc = GetXPathDocument (path);
363 XPathNavigator nav = doc.CreateNavigator ();
364 CultureInfoEntry ci = new CultureInfoEntry ();
365 string supp;
366 string loc;
368 // ci.Name = locale; // TODO: Some of these need to be mapped.
370 // First thing we do is get the lang-territory combo, lcid, and full names
371 ci.Language = nav.Evaluate ("string (ldml/identity/language/@type)").ToString ();
372 ci.Territory = nav.Evaluate ("string (ldml/identity/territory/@type)").ToString ();
374 if (!LookupLcids (ci, is_language))
375 return null;
376 LookupNames (ci);
379 * Locale generation is done in six steps, first we
380 * read the root file which is the base invariant data
381 * then the supplemental root data,
382 * then the language file, the supplemental languages
383 * file then the locale file, then the supplemental
384 * locale file. Values in each descending file can
385 * overwrite previous values.
387 doc = GetXPathDocument (Path.Combine ("langs", "root.xml"));
388 nav = doc.CreateNavigator ();
389 Lookup (nav, ci);
391 doc = GetXPathDocument (Path.Combine ("supp", "root.xml"));
392 nav = doc.CreateNavigator ();
393 Lookup (nav, ci);
395 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (GetLanguageFixed (ci)) + ".xml"));
396 nav = doc.CreateNavigator ();
397 Lookup (nav, ci);
399 supp = Path.Combine ("supp", GetLanguageFixed (ci) + ".xml");
400 if (File.Exists (supp)) {
401 doc = GetXPathDocument (supp);
402 nav = doc.CreateNavigator ();
403 Lookup (nav, ci);
406 loc = Path.Combine ("locales", locale + ".xml");
407 if (File.Exists (loc)) {
408 doc = GetXPathDocument (loc);
409 nav = doc.CreateNavigator ();
410 Lookup (nav, ci);
413 supp = Path.Combine ("supp", locale + ".xml");
414 if (File.Exists (supp)) {
415 doc = GetXPathDocument (supp);
416 nav = doc.CreateNavigator ();
417 Lookup (nav, ci);
420 return ci;
423 private void Lookup (XPathNavigator nav, CultureInfoEntry ci)
425 LookupDateTimeInfo (nav, ci);
426 LookupNumberInfo (nav, ci);
429 private string GetLanguageFixed (CultureInfoEntry ci)
431 // This is a hack, but without it nb-NO and nn-NO won't work.
432 if (ci.Territory == "NO") {
433 switch (ci.Language) {
434 case "nb":
435 case "nn":
436 return "no";
439 return ci.Language;
442 private void LookupNames (CultureInfoEntry ci)
444 XPathDocument doc = GetXPathDocument (Path.Combine ("langs", GetShortName (Lang) + ".xml"));
445 XPathNavigator nav = doc.CreateNavigator ();
447 ci.DisplayName = LookupFullName (ci, nav);
449 if (Lang == "en") {
450 ci.EnglishName = ci.DisplayName;
451 } else {
452 doc = GetXPathDocument (Path.Combine ("langs", "en.xml"));
453 nav = doc.CreateNavigator ();
454 ci.EnglishName = LookupFullName (ci, nav);
457 if (ci.Language == Lang) {
458 ci.NativeName = ci.DisplayName;
459 } else {
460 // FIXME: We use ci.Language here.
461 // This is nothing more than hack for nb-NO and nn-NO
462 // where Parent of them is nn (not nb or nn).
463 string lang = ci.Language;
464 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
465 nav = doc.CreateNavigator ();
466 ci.NativeName = LookupFullName (ci, nav);
470 private void AddPattern (ArrayList al, string pattern)
472 if (!al.Contains (pattern))
473 al.Add (pattern);
476 private void LookupDateTimeInfo (XPathNavigator nav, CultureInfoEntry ci)
479 * TODO: Does anyone have multiple calendars?
481 XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("ldml/dates/calendars/calendar");
483 while (ni.MoveNext ()) {
484 DateTimeFormatEntry df = ci.DateTimeFormatEntry;
485 string cal_type = ni.Current.GetAttribute ("type", String.Empty);
487 if (cal_type != String.Empty)
488 df.CalendarType = cal_type;
490 XPathNodeIterator ni2 = (XPathNodeIterator) ni.Current.Evaluate ("optionalCalendars/calendar");
491 int opt_cal_count = 0;
492 while (ni2.MoveNext ()) {
493 int type;
494 string greg_type_str;
495 XPathNavigator df_nav = ni2.Current;
496 switch (df_nav.GetAttribute ("type", String.Empty)) {
497 case "Gregorian":
498 type = 0;
499 break;
500 case "Hijri":
501 type = 0x01;
502 break;
503 case "ThaiBuddhist":
504 type = 0x02;
505 break;
506 default:
507 Console.WriteLine ("unknown calendar type: " +
508 df_nav.GetAttribute ("type", String.Empty));
509 continue;
511 type <<= 24;
512 greg_type_str = df_nav.GetAttribute ("greg_type", String.Empty);
513 if (greg_type_str != null && greg_type_str != String.Empty) {
514 GregorianCalendarTypes greg_type = (GregorianCalendarTypes)
515 Enum.Parse (typeof (GregorianCalendarTypes), greg_type_str);
516 int greg_type_int = (int) greg_type;
517 type |= greg_type_int;
520 Console.WriteLine ("Setting cal type: {0:X} for {1}", type, ci.Name);
521 ci.CalendarData [opt_cal_count++] = type;
524 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("monthNames/month");
525 while (ni2.MoveNext ()) {
526 if (ni2.CurrentPosition == 1)
527 df.MonthNames.Clear ();
528 df.MonthNames.Add (ni2.Current.Value);
530 if (df.MonthNames.Count == 12)
531 df.MonthNames.Add (String.Empty);
533 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dayNames/day");
534 while (ni2.MoveNext ()) {
535 if (ni2.CurrentPosition == 1)
536 df.DayNames.Clear ();
537 df.DayNames.Add (ni2.Current.Value);
540 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dayAbbr/day");
541 while (ni2.MoveNext ()) {
542 if (ni2.CurrentPosition == 1)
543 df.AbbreviatedDayNames.Clear ();
544 df.AbbreviatedDayNames.Add (ni2.Current.Value);
547 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("monthAbbr/month");
548 while (ni2.MoveNext ()) {
549 if (ni2.CurrentPosition == 1)
550 df.AbbreviatedMonthNames.Clear ();
551 df.AbbreviatedMonthNames.Add (ni2.Current.Value);
553 if (df.AbbreviatedMonthNames.Count == 12)
554 df.AbbreviatedMonthNames.Add (String.Empty);
556 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dateFormats/dateFormatLength");
557 while (ni2.MoveNext ()) {
558 XPathNavigator df_nav = ni2.Current;
559 XPathNodeIterator p = df_nav.Select ("dateFormat/pattern");
560 string value = null;
561 if (p.MoveNext ())
562 value = p.Current.Value;
563 XPathNodeIterator ext = null;
564 switch (df_nav.GetAttribute ("type", String.Empty)) {
565 case "full":
566 if (value != null)
567 ParseFullDateFormat (df, value);
568 break;
569 case "long":
570 if (value != null)
571 df.LongDatePattern = value;
572 ext = df_nav.Select ("extraPatterns/pattern");
573 if (ext.MoveNext ()) {
574 df.LongDatePatterns.Clear ();
575 AddPattern (df.LongDatePatterns, df.LongDatePattern);
576 do {
577 df.LongDatePatterns.Add (ext.Current.Value);
578 } while (ext.MoveNext ());
580 else
581 AddPattern (df.LongDatePatterns, df.LongDatePattern);
582 break;
583 case "short":
584 if (value != null)
585 df.ShortDatePattern = value;
586 ext = df_nav.Select ("extraPatterns/pattern");
587 if (ext.MoveNext ()) {
588 df.ShortDatePatterns.Clear ();
589 AddPattern (df.ShortDatePatterns, df.ShortDatePattern);
590 do {
591 df.ShortDatePatterns.Add (ext.Current.Value);
592 } while (ext.MoveNext ());
594 else
595 AddPattern (df.ShortDatePatterns, df.ShortDatePattern);
596 break;
597 case "year_month":
598 if (value != null)
599 df.YearMonthPattern = value;
600 break;
601 case "month_day":
602 if (value != null)
603 df.MonthDayPattern = value;
604 break;
608 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("timeFormats/timeFormatLength");
609 while (ni2.MoveNext ()) {
610 XPathNavigator df_nav = ni2.Current;
611 XPathNodeIterator p = df_nav.Select ("timeFormat/pattern");
612 string value = null;
613 if (p.MoveNext ())
614 value = p.Current.Value;
615 XPathNodeIterator ext = null;
616 switch (df_nav.GetAttribute ("type", String.Empty)) {
617 case "long":
618 if (value != null)
619 df.LongTimePattern = value.Replace ('a', 't');
620 ext = df_nav.Select ("extraPatterns/pattern");
621 if (ext.MoveNext ()) {
622 df.LongTimePatterns.Clear ();
623 AddPattern (df.LongTimePatterns, df.LongTimePattern);
624 do {
625 df.LongTimePatterns.Add (ext.Current.Value);
626 } while (ext.MoveNext ());
628 else
629 AddPattern (df.LongTimePatterns, df.LongTimePattern);
630 break;
631 case "short":
632 if (value != null)
633 df.ShortTimePattern = value.Replace ('a', 't');
634 ext = df_nav.Select ("extraPatterns/pattern");
635 if (ext.MoveNext ()) {
636 df.ShortTimePatterns.Clear ();
637 AddPattern (df.ShortTimePatterns, df.ShortTimePattern);
638 do {
639 df.ShortTimePatterns.Add (ext.Current.Value);
640 } while (ext.MoveNext ());
642 else
643 AddPattern (df.ShortTimePatterns, df.ShortTimePattern);
644 break;
648 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dateTimeFormats/dateTimeFormatLength/dateTimeFormat/pattern");
649 if (ni2.MoveNext ())
650 df.RawFullDateTimePattern = ni2.Current.ToString ();/*String.Format (ni2.Current.ToString (),
651 df.LongTimePattern, df.LongDatePattern);*/
653 XPathNodeIterator am = ni.Current.SelectChildren ("am", "");
654 if (am.MoveNext ())
655 df.AMDesignator = am.Current.Value;
656 XPathNodeIterator pm = ni.Current.SelectChildren ("pm", "");
657 if (pm.MoveNext ())
658 df.PMDesignator = pm.Current.Value;
660 string am = (string) ni.Current.Evaluate ("string(am)");
661 string pm = (string) ni.Current.Evaluate ("string(pm)");
663 if (am != String.Empty)
664 df.AMDesignator = am;
665 if (pm != String.Empty)
666 df.PMDesignator = pm;
668 ni2 = (XPathNodeIterator) ni.Current.Evaluate
669 ("week/firstDay");
670 if (ni2.MoveNext ()) {
671 XPathNavigator weekday_nav = ni2.Current;
672 switch (weekday_nav.GetAttribute ("day", String.Empty)) {
673 case "sun":
674 df.FirstDayOfWeek = 0;
675 break;
676 case "mon":
677 df.FirstDayOfWeek = 1;
678 break;
679 case "tue":
680 df.FirstDayOfWeek = 2;
681 break;
682 case "wed":
683 df.FirstDayOfWeek = 3;
684 break;
685 case "thu":
686 df.FirstDayOfWeek = 4;
687 break;
688 case "fri":
689 df.FirstDayOfWeek = 5;
690 break;
691 case "sat":
692 df.FirstDayOfWeek = 6;
693 break;
698 string date_sep = (string) nav.Evaluate ("string(ldml/dates/symbols/dateSeparator)");
699 string time_sep = (string) nav.Evaluate ("string(ldml/dates/symbols/timeSeparator)");
701 if (date_sep != String.Empty)
702 ci.DateTimeFormatEntry.DateSeparator = date_sep;
703 if (time_sep != String.Empty)
704 ci.DateTimeFormatEntry.TimeSeparator = time_sep;
707 private void LookupNumberInfo (XPathNavigator nav, CultureInfoEntry ci)
709 XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("ldml/numbers");
711 while (ni.MoveNext ()) {
712 LookupNumberSymbols (ni.Current, ci);
713 LookupDecimalFormat (ni.Current, ci);
714 LookupPercentFormat (ni.Current, ci);
715 LookupCurrencyFormat (ni.Current, ci);
716 LookupCurrencySymbol (ni.Current, ci);
720 private void LookupDecimalFormat (XPathNavigator nav, CultureInfoEntry ci)
722 string format = (string) nav.Evaluate ("string(decimalFormats/" +
723 "decimalFormatLength/decimalFormat/pattern)");
725 if (format == String.Empty)
726 return;
728 string [] part_one, part_two;
729 string [] pos_neg = format.Split (new char [1] {';'}, 2);
731 // Most of the patterns are common in positive and negative
732 if (pos_neg.Length == 1)
733 pos_neg = new string [] {pos_neg [0], pos_neg [0]};
735 if (pos_neg.Length == 2) {
737 part_one = pos_neg [0].Split (new char [1] {'.'}, 2);
738 if (part_one.Length == 1)
739 part_one = new string [] {part_one [0], String.Empty};
741 if (part_one.Length == 2) {
742 // assumed same for both positive and negative
743 // decimal digit side
744 ci.NumberFormatEntry.NumberDecimalDigits = 0;
745 for (int i = 0; i < part_one [1].Length; i++) {
746 if (part_one [1][i] == '#') {
747 ci.NumberFormatEntry.NumberDecimalDigits ++;
748 } else
749 break; }
750 // FIXME: This should be actually done by modifying culture xml files, but too many files to be modified.
751 if (ci.NumberFormatEntry.NumberDecimalDigits > 0)
752 ci.NumberFormatEntry.NumberDecimalDigits --;
754 // decimal grouping side
755 part_two = part_one [0].Split (',');
756 if (part_two.Length > 1) {
757 int len = part_two.Length - 1;
758 ci.NumberFormatEntry.NumberGroupSizes = new int [len];
759 for (int i = 0; i < len; i++) {
760 string pat = part_two [i + 1];
761 ci.NumberFormatEntry.NumberGroupSizes [i] = pat.Length;
763 } else {
764 ci.NumberFormatEntry.NumberGroupSizes = new int [1] { 3 };
767 if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith (")")) {
768 ci.NumberFormatEntry.NumberNegativePattern = 0;
769 } else if (pos_neg [1].StartsWith ("- ")) {
770 ci.NumberFormatEntry.NumberNegativePattern = 2;
771 } else if (pos_neg [1].StartsWith ("-")) {
772 ci.NumberFormatEntry.NumberNegativePattern = 1;
773 } else if (pos_neg [1].EndsWith (" -")) {
774 ci.NumberFormatEntry.NumberNegativePattern = 4;
775 } else if (pos_neg [1].EndsWith ("-")) {
776 ci.NumberFormatEntry.NumberNegativePattern = 3;
777 } else {
778 ci.NumberFormatEntry.NumberNegativePattern = 1;
784 private void LookupPercentFormat (XPathNavigator nav, CultureInfoEntry ci)
786 string format = (string) nav.Evaluate ("string(percentFormats/" +
787 "percentFormatLength/percentFormat/pattern)");
789 if (format == String.Empty)
790 return;
792 string [] part_one, part_two;
794 // we don't have percentNegativePattern in CLDR so
795 // the percentNegativePattern are just guesses
796 if (format.StartsWith ("%")) {
797 ci.NumberFormatEntry.PercentPositivePattern = 2;
798 ci.NumberFormatEntry.PercentNegativePattern = 2;
799 format = format.Substring (1);
800 } else if (format.EndsWith (" %")) {
801 ci.NumberFormatEntry.PercentPositivePattern = 0;
802 ci.NumberFormatEntry.PercentNegativePattern = 0;
803 format = format.Substring (0, format.Length - 2);
804 } else if (format.EndsWith ("%")) {
805 ci.NumberFormatEntry.PercentPositivePattern = 1;
806 ci.NumberFormatEntry.PercentNegativePattern = 1;
807 format = format.Substring (0, format.Length - 1);
808 } else {
809 ci.NumberFormatEntry.PercentPositivePattern = 0;
810 ci.NumberFormatEntry.PercentNegativePattern = 0;
813 part_one = format.Split (new char [1] {'.'}, 2);
814 if (part_one.Length == 2) {
815 // assumed same for both positive and negative
816 // decimal digit side
817 ci.NumberFormatEntry.PercentDecimalDigits = 0;
818 for (int i = 0; i < part_one [1].Length; i++) {
819 if (part_one [1][i] == '#')
820 ci.NumberFormatEntry.PercentDecimalDigits++;
821 else
822 break;
826 if (part_one.Length > 0) {
827 // percent grouping side
828 part_two = part_one [0].Split (',');
829 if (part_two.Length > 1) {
830 int len = part_two.Length - 1;
831 ci.NumberFormatEntry.PercentGroupSizes = new int [len];
832 for (int i = 0; i < len; i++) {
833 string pat = part_two [i + 1];
834 if (pat [pat.Length -1] == '0')
835 ci.NumberFormatEntry.PercentDecimalDigits = pat.Length - 1;
836 ci.NumberFormatEntry.PercentGroupSizes [i] = pat.Length;
838 } else {
839 ci.NumberFormatEntry.PercentGroupSizes = new int [1] { 3 };
840 ci.NumberFormatEntry.PercentDecimalDigits = 2;
845 private void LookupCurrencyFormat (XPathNavigator nav, CultureInfoEntry ci)
847 string format = (string) nav.Evaluate ("string(currencyFormats/" +
848 "currencyFormatLength/currencyFormat/pattern)");
850 if (format == String.Empty)
851 return;
853 string [] part_one, part_two;
854 string [] pos_neg = format.Split (new char [1] {';'}, 2);
856 // Most of the patterns are common in positive and negative
857 if (pos_neg.Length == 1)
858 pos_neg = new string [] {pos_neg [0], pos_neg [0]};
860 if (pos_neg.Length == 2) {
861 part_one = pos_neg [0].Split (new char [1] {'.'}, 2);
862 if (part_one.Length == 1)
863 part_one = new string [] {part_one [0], String.Empty};
864 if (part_one.Length == 2) {
865 // assumed same for both positive and negative
866 // decimal digit side
867 ci.NumberFormatEntry.CurrencyDecimalDigits = 0;
868 for (int i = 0; i < part_one [1].Length; i++) {
869 if (part_one [1][i] == '0')
870 ci.NumberFormatEntry.CurrencyDecimalDigits++;
871 else
872 break;
875 // decimal grouping side
876 part_two = part_one [0].Split (',');
877 if (part_two.Length > 1) {
878 int len = part_two.Length - 1;
879 ci.NumberFormatEntry.CurrencyGroupSizes = new int [len];
880 for (int i = 0; i < len; i++) {
881 string pat = part_two [i + 1];
882 ci.NumberFormatEntry.CurrencyGroupSizes [i] = pat.Length;
884 } else {
885 ci.NumberFormatEntry.CurrencyGroupSizes = new int [1] { 3 };
888 if (pos_neg [1].StartsWith ("(\u00a4 ") && pos_neg [1].EndsWith (")")) {
889 ci.NumberFormatEntry.CurrencyNegativePattern = 14;
890 } else if (pos_neg [1].StartsWith ("(\u00a4") && pos_neg [1].EndsWith (")")) {
891 ci.NumberFormatEntry.CurrencyNegativePattern = 0;
892 } else if (pos_neg [1].StartsWith ("\u00a4 ") && pos_neg [1].EndsWith ("-")) {
893 ci.NumberFormatEntry.CurrencyNegativePattern = 11;
894 } else if (pos_neg [1].StartsWith ("\u00a4") && pos_neg [1].EndsWith ("-")) {
895 ci.NumberFormatEntry.CurrencyNegativePattern = 3;
896 } else if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith (" \u00a4")) {
897 ci.NumberFormatEntry.CurrencyNegativePattern = 15;
898 } else if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith ("\u00a4")) {
899 ci.NumberFormatEntry.CurrencyNegativePattern = 4;
900 } else if (pos_neg [1].StartsWith ("-") && pos_neg [1].EndsWith (" \u00a4")) {
901 ci.NumberFormatEntry.CurrencyNegativePattern = 8;
902 } else if (pos_neg [1].StartsWith ("-") && pos_neg [1].EndsWith ("\u00a4")) {
903 ci.NumberFormatEntry.CurrencyNegativePattern = 5;
904 } else if (pos_neg [1].StartsWith ("-\u00a4 ")) {
905 ci.NumberFormatEntry.CurrencyNegativePattern = 9;
906 } else if (pos_neg [1].StartsWith ("-\u00a4")) {
907 ci.NumberFormatEntry.CurrencyNegativePattern = 1;
908 } else if (pos_neg [1].StartsWith ("\u00a4 -")) {
909 ci.NumberFormatEntry.CurrencyNegativePattern = 12;
910 } else if (pos_neg [1].StartsWith ("\u00a4-")) {
911 ci.NumberFormatEntry.CurrencyNegativePattern = 2;
912 } else if (pos_neg [1].EndsWith (" \u00a4-")) {
913 ci.NumberFormatEntry.CurrencyNegativePattern = 10;
914 } else if (pos_neg [1].EndsWith ("\u00a4-")) {
915 ci.NumberFormatEntry.CurrencyNegativePattern = 7;
916 } else if (pos_neg [1].EndsWith ("- \u00a4")) {
917 ci.NumberFormatEntry.CurrencyNegativePattern = 13;
918 } else if (pos_neg [1].EndsWith ("-\u00a4")) {
919 ci.NumberFormatEntry.CurrencyNegativePattern = 6;
920 } else {
921 ci.NumberFormatEntry.CurrencyNegativePattern = 0;
924 if (pos_neg [0].StartsWith ("\u00a4 ")) {
925 ci.NumberFormatEntry.CurrencyPositivePattern = 2;
926 } else if (pos_neg [0].StartsWith ("\u00a4")) {
927 ci.NumberFormatEntry.CurrencyPositivePattern = 0;
928 } else if (pos_neg [0].EndsWith (" \u00a4")) {
929 ci.NumberFormatEntry.CurrencyPositivePattern = 3;
930 } else if (pos_neg [0].EndsWith ("\u00a4")) {
931 ci.NumberFormatEntry.CurrencyPositivePattern = 1;
932 } else {
933 ci.NumberFormatEntry.CurrencyPositivePattern = 0;
939 private void LookupNumberSymbols (XPathNavigator nav, CultureInfoEntry ci)
941 string dec = (string) nav.Evaluate ("string(symbols/decimal)");
942 string group = (string) nav.Evaluate ("string(symbols/group)");
943 string percent = (string) nav.Evaluate ("string(symbols/percentSign)");
944 string positive = (string) nav.Evaluate ("string(symbols/plusSign)");
945 string negative = (string) nav.Evaluate ("string(symbols/minusSign)");
946 string per_mille = (string) nav.Evaluate ("string(symbols/perMille)");
947 string infinity = (string) nav.Evaluate ("string(symbols/infinity)");
948 string nan = (string) nav.Evaluate ("string(symbols/nan)");
950 if (dec != String.Empty) {
951 ci.NumberFormatEntry.NumberDecimalSeparator = dec;
952 ci.NumberFormatEntry.PercentDecimalSeparator = dec;
953 ci.NumberFormatEntry.CurrencyDecimalSeparator = dec;
956 if (group != String.Empty) {
957 ci.NumberFormatEntry.NumberGroupSeparator = group;
958 ci.NumberFormatEntry.PercentGroupSeparator = group;
959 ci.NumberFormatEntry.CurrencyGroupSeparator = group;
962 if (percent != String.Empty)
963 ci.NumberFormatEntry.PercentSymbol = percent;
964 if (positive != String.Empty)
965 ci.NumberFormatEntry.PositiveSign = positive;
966 if (negative != String.Empty)
967 ci.NumberFormatEntry.NegativeSign = negative;
968 if (per_mille != String.Empty)
969 ci.NumberFormatEntry.PerMilleSymbol = per_mille;
970 if (infinity != String.Empty)
971 ci.NumberFormatEntry.PositiveInfinitySymbol = infinity;
972 if (nan != String.Empty)
973 ci.NumberFormatEntry.NaNSymbol = nan;
976 private void LookupCurrencySymbol (XPathNavigator nav, CultureInfoEntry ci)
978 string type = currency_types [ci.Territory] as string;
980 if (type == null) {
981 Console.WriteLine ("no currency type for: " + ci.Territory);
982 return;
985 string cur = (string) nav.Evaluate ("string(currencies/currency [@type='" +
986 type + "']/symbol)");
988 if (cur != String.Empty)
989 ci.NumberFormatEntry.CurrencySymbol = cur;
992 private bool LookupLcids (CultureInfoEntry ci, bool lang)
994 XPathNavigator nav = lcids_doc.CreateNavigator ();
995 string name = ci.Name;
996 // Language name does not always consist of locale name.
997 // (for zh-* it must be either zh-CHS or zh-CHT)
998 string langName = GetLanguageFixed (ci);
1000 // if (ci.Territory != null)
1001 // name += "-" + ci.Territory;
1003 XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("lcids/lcid[@name='"
1004 + (lang ? langName : name) + "']");
1005 if (!ni.MoveNext ()) {
1006 Console.WriteLine ("no lcid found for: {0} ({1}/{2})", name, ci.Language, ci.Territory);
1007 string file;
1009 if (ci.Territory != null) {
1010 file = Path.Combine ("locales", ci.Language + "_" + ci.Territory + ".xml");
1011 Console.WriteLine ("deleting file: " + file);
1012 File.Delete (file);
1015 return false;
1018 string id = ni.Current.GetAttribute ("id", String.Empty);
1019 string parent = ni.Current.GetAttribute ("parent", String.Empty);
1020 string specific = ni.Current.GetAttribute ("specific", String.Empty);
1021 string iso2 = ni.Current.GetAttribute ("iso2", String.Empty);
1022 string iso3 = ni.Current.GetAttribute ("iso3", String.Empty);
1023 string win = ni.Current.GetAttribute ("win", String.Empty);
1024 string icu = ni.Current.GetAttribute ("icu_name", String.Empty);
1026 // lcids are in 0x<hex> format
1027 ci.Lcid = id;
1028 ci.ParentLcid = parent;
1029 ci.SpecificLcid = specific;
1030 ci.ISO2Lang = iso2;
1031 ci.ISO3Lang = iso3;
1032 ci.Win3Lang = win;
1033 ci.IcuName = icu;
1035 ci.TextInfoEntry = new TextInfoEntry (int.Parse (id.Substring (2), NumberStyles.HexNumber), GetXPathDocument ("textinfo.xml"));
1037 return true;
1040 private string LookupFullName (CultureInfoEntry ci, XPathNavigator nav)
1042 string pre = "ldml/localeDisplayNames/";
1043 string ret;
1045 // FIXME: We use ci.Language here.
1046 // This is nothing more than hack for nb-NO or nn-NO
1047 // where Parent of them is nn (not nb or nn).
1048 ret = (string) nav.Evaluate ("string("+
1049 pre + "languages/language[@type='" + GetShortName (ci.Language) + "'])");
1051 if (ci.Territory == null)
1052 return ret;
1053 ret += " (" + (string) nav.Evaluate ("string("+
1054 pre + "territories/territory[@type='" + ci.Territory + "'])") + ")";
1056 return ret;
1059 private void LookupRegions ()
1061 XPathDocument doc = GetXPathDocument ("supplementalData.xml");
1062 XPathNavigator nav = doc.CreateNavigator ();
1063 XPathNodeIterator ni = nav.Select ("supplementalData/currencyData/region");
1064 while (ni.MoveNext ()) {
1065 string territory = (string) ni.Current.GetAttribute ("iso3166", String.Empty);
1066 string currency = (string) ni.Current.Evaluate ("string(currency/@iso4217)");
1067 RegionInfoEntry region = new RegionInfoEntry ();
1068 region.ISO2Name = territory.ToUpper ();
1069 region.ISOCurrencySymbol = currency;
1070 regions [territory] = region;
1073 doc = GetXPathDocument ("langs/en.xml");
1074 nav = doc.CreateNavigator ();
1075 ni = nav.Select ("/ldml/localeDisplayNames/territories/territory");
1076 while (ni.MoveNext ()) {
1077 RegionInfoEntry r = (RegionInfoEntry)
1078 regions [ni.Current.GetAttribute ("type", "")];
1079 if (r == null)
1080 continue;
1081 r.EnglishName = ni.Current.Value;
1084 Hashtable curNames = new Hashtable ();
1085 ni = nav.Select ("/ldml/numbers/currencies/currency");
1086 while (ni.MoveNext ())
1087 curNames [ni.Current.GetAttribute ("type", "")] =
1088 ni.Current.Evaluate ("string (displayName)");
1090 foreach (RegionInfoEntry r in regions.Values)
1091 r.CurrencyEnglishName =
1092 (string) curNames [r.ISOCurrencySymbol];
1095 private void LookupCurrencyTypes ()
1097 XPathDocument doc = GetXPathDocument ("supplementalData.xml");
1098 XPathNavigator nav = doc.CreateNavigator ();
1100 currency_types = new Hashtable ();
1102 XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("supplementalData/currencyData/region");
1103 while (ni.MoveNext ()) {
1104 string territory = (string) ni.Current.GetAttribute ("iso3166", String.Empty);
1105 string currency = (string) ni.Current.Evaluate ("string(currency/@iso4217)");
1106 currency_types [territory] = currency;
1110 static string control_chars = "eghmsftz";
1112 // HACK: We are trying to build year_month and month_day patterns from the full pattern.
1113 private void ParseFullDateFormat (DateTimeFormatEntry df, string full)
1116 string month_day = String.Empty;
1117 string year_month = String.Empty;
1118 bool in_month_data = false;
1119 bool in_year_data = false;
1120 int day_start = 0, day_end = 0;
1121 int month_start = 0, month_end = 0;
1122 int year_start = 0, year_end = 0;
1123 bool inquote = false;
1125 for (int i = 0; i < full.Length; i++) {
1126 char c = full [i];
1127 if (!inquote && c == 'M') {
1128 month_day += c;
1129 year_month += c;
1130 in_year_data = true;
1131 in_month_data = true;
1132 if (month_start == 0)
1133 month_start = i;
1134 month_end = i;
1135 year_end = year_month.Length;
1136 } else if (!inquote && Char.ToLower (c) == 'd') {
1137 month_day += c;
1138 in_month_data = true;
1139 in_year_data = false;
1140 if (day_start == 0)
1141 day_start = i;
1142 day_end = i;
1143 } else if (!inquote && Char.ToLower (c) == 'y') {
1144 year_month += c;
1145 in_year_data = true;
1146 in_month_data = false;
1147 if (year_start == 0)
1148 year_start = i;
1149 year_end = i;
1150 } else if (!inquote && control_chars.IndexOf (Char.ToLower (c)) >= 0) {
1151 in_year_data = false;
1152 in_month_data = false;
1153 } else if (in_year_data || in_month_data) {
1154 if (in_month_data)
1155 month_day += c;
1156 if (in_year_data)
1157 year_month += c;
1160 if (c == '\'') {
1161 inquote = !inquote;
1165 if (month_day != String.Empty) {
1166 //month_day = month_day.Substring (0, month_end);
1167 df.MonthDayPattern = TrimPattern (month_day);
1169 if (year_month != String.Empty) {
1170 //year_month = year_month.Substring (0, year_end);
1171 df.YearMonthPattern = TrimPattern (year_month);
1175 string TrimPattern (string p)
1177 int idx = 0;
1178 p = p.Trim ().TrimEnd (',');
1179 idx = p.LastIndexOf ("' de '"); // spanish dates
1180 if (idx > 0)
1181 p = p.Substring (0, idx);
1182 idx = p.LastIndexOf ("' ta '"); // finnish
1183 if (idx > 0)
1184 p = p.Substring (0, idx);
1185 idx = p.LastIndexOf ("'ren'"); // euskara
1186 if (idx > 0)
1187 p = p.Replace ("'ren'", "").Trim ();
1188 idx = p.LastIndexOf ("'a'"); // estonian
1189 if (idx > 0)
1190 p = p.Substring (0, idx);
1192 return p.Replace ("'ta '", "'ta'"); // finnish
1195 private class LcidComparer : IComparer {
1197 public int Compare (object a, object b)
1199 CultureInfoEntry aa = (CultureInfoEntry) a;
1200 CultureInfoEntry bb = (CultureInfoEntry) b;
1202 return aa.Lcid.CompareTo (bb.Lcid);
1206 private class NameComparer : IComparer {
1208 public int Compare (object a, object b)
1210 CultureInfoEntry aa = (CultureInfoEntry) a;
1211 CultureInfoEntry bb = (CultureInfoEntry) b;
1213 return String.CompareOrdinal(aa.Name.ToLower (), bb.Name.ToLower ());
1217 class RegionComparer : IComparer
1219 public static RegionComparer Instance = new RegionComparer ();
1221 public int Compare (object o1, object o2)
1223 RegionInfoEntry r1 = (RegionInfoEntry) o1;
1224 RegionInfoEntry r2 = (RegionInfoEntry) o2;
1225 return String.CompareOrdinal (
1226 r1.ISO2Name, r2.ISO2Name);
1230 class RegionLCIDMap
1232 public RegionLCIDMap (int lcid, int regionId)
1234 LCID = lcid;
1235 RegionId = regionId;
1238 public int LCID;
1239 public int RegionId;