2 // Mono.Tools.LocalBuilder.Driver
5 // Jackson Harper (jackson@ximian.com)
6 // Atsushi Enomoto (atsushi@ximian.com)
8 // (C) 2004-2005 Novell, Inc (http://www.novell.com)
16 using System.Xml.XPath;
17 using System.Collections;
18 using System.Globalization;
19 using System.Text.RegularExpressions;
21 namespace Mono.Tools.LocaleBuilder {
25 public static void Main (string [] args)
27 Driver d = new Driver ();
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)
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];
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 // The lang is the language that display names will be displayed in
62 public string Locales {
63 get { return locales; }
64 set { locales = value; }
67 public string HeaderFileName {
69 if (header_name == null)
70 return "culture-info-tables.h";
73 set { header_name = value; }
78 Regex locales_regex = null;
80 locales_regex = new Regex (Locales);
82 langs = new Hashtable ();
83 cultures = new ArrayList ();
84 regions = new Hashtable ();
88 LookupCurrencyTypes ();
90 foreach (string file in Directory.GetFiles ("locales", "*.xml")) {
91 string fn = Path.GetFileNameWithoutExtension (file);
93 continue; // see bug #75499
94 if (locales_regex == null || locales_regex.IsMatch (fn)) {
99 /* FIXME: This is hacky.
100 * Since there is only langs/zh.xml while there are
101 * two "zh" languages (CHS and CHT), there should be
102 * different language profiles and we are not likely
103 * to add lang/* files. So here I just clone zh-CHS
106 foreach (CultureInfoEntry e in cultures) {
107 if (e.Name == "zh-CHS") {
109 CultureInfoEntry.ShallowCopy (e);
110 t.Language = "zh-CHT";
111 LookupLcids (t, true);
117 ArrayList regionList = new ArrayList (regions.Values);
118 regionList.Sort (RegionComparer.Instance);
120 foreach (RegionInfoEntry r in regionList)
121 r.RegionId = number++;
123 foreach (CultureInfoEntry e in cultures) {
124 int lcid = int.Parse (e.Lcid.Substring (2),
125 NumberStyles.HexNumber);
127 int start = e.Name.IndexOf ('-') + 1;
130 for (idx = start; idx < e.Name.Length; idx++)
131 if (!Char.IsLetter (e.Name [idx]))
134 Console.Error.WriteLine ("Culture {0} {1} is not mappable to Region.", e.Lcid, e.Name);
137 string name = e.Name.Substring (start, idx - start);
138 RegionInfoEntry rm = null;
139 foreach (RegionInfoEntry r in regions.Values)
140 if (r.ISO2Name == name) {
145 Console.Error.WriteLine ("No definition for region {0}", name);
148 e.RegionId = rm.RegionId;
152 * Dump each table individually. Using StringBuilders
153 * because it is easier to debug, should switch to just
154 * writing to streams eventually.
156 using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
157 writer.NewLine = "\n";
159 writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
160 writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
161 writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
162 writer.WriteLine ("\n");
164 writer.WriteLine ("#define NUM_CULTURE_ENTRIES " + cultures.Count);
165 writer.WriteLine ("#define NUM_REGION_ENTRIES " + regionList.Count);
166 writer.WriteLine ("\n");
168 // Sort the cultures by lcid
169 cultures.Sort (new LcidComparer ());
171 StringBuilder builder = new StringBuilder ();
173 int count = cultures.Count;
174 for (int i = 0; i < count; i++) {
175 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
176 if (ci.DateTimeFormatEntry == null)
178 ci.DateTimeFormatEntry.AppendTableRow (builder);
179 ci.DateTimeFormatEntry.Row = row++;
181 builder.Append (',');
182 builder.Append ('\n');
185 writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
186 writer.Write (builder);
187 writer.WriteLine ("};\n\n");
189 builder = new StringBuilder ();
191 for (int i=0; i < count; i++) {
192 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
193 if (ci.NumberFormatEntry == null)
195 ci.NumberFormatEntry.AppendTableRow (builder);
196 ci.NumberFormatEntry.Row = row++;
198 builder.Append (',');
199 builder.Append ('\n');
202 writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
203 writer.Write (builder);
204 writer.WriteLine ("};\n\n");
206 builder = new StringBuilder ();
208 for (int i = 0; i < count; i++) {
209 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
210 ci.AppendTableRow (builder);
213 builder.Append (',');
214 builder.Append ('\n');
217 writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
218 writer.Write (builder);
219 writer.WriteLine ("};\n\n");
221 cultures.Sort (new NameComparer ()); // Sort based on name
222 builder = new StringBuilder ();
223 for (int i = 0; i < count; i++) {
224 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
225 builder.Append ("\t{" + Entry.EncodeStringIdx (ci.Name.ToLower ()) + ", ");
226 builder.Append (ci.Row + "}");
228 builder.Append (',');
229 builder.Append ('\n');
232 writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
233 writer.Write (builder);
234 writer.WriteLine ("};\n\n");
236 builder = new StringBuilder ();
238 foreach (RegionInfoEntry r in regionList) {
239 r.AppendTableRow (builder);
240 if (++rcount != regionList.Count)
241 builder.Append (',');
242 builder.Append ('\n');
244 writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
245 writer.Write (builder);
246 writer.WriteLine ("};\n\n");
248 builder = new StringBuilder ();
250 foreach (RegionInfoEntry ri in regionList) {
251 builder.Append ("\t{" + Entry.EncodeStringIdx (ri.ISO2Name) + ", ");
252 builder.Append (ri.RegionId + "}");
253 if (++rcount < regionList.Count)
254 builder.Append (',');
255 builder.Append ('\n');
258 writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
259 writer.Write (builder);
260 writer.WriteLine ("};\n\n");
262 writer.WriteLine ("static const char locale_strings [] = {");
263 writer.Write (Entry.GetStrings ());
264 writer.WriteLine ("};\n\n");
266 writer.WriteLine ("#endif\n");
270 private XPathDocument GetXPathDocument (string path)
272 XmlTextReader xtr = new XmlTextReader (path);
273 xtr.XmlResolver = null;
274 return new XPathDocument (xtr);
277 private string GetShortName (string lang)
279 return lang == "zh-CHS" ? "zh" : lang;
282 private bool ParseLang (string lang)
284 XPathDocument doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
285 XPathNavigator nav = doc.CreateNavigator ();
286 CultureInfoEntry ci = new CultureInfoEntry ();
287 string lang_type, terr_type;
289 // ci.Name = lang; // TODO: might need to be mapped.
291 lang_type = nav.Evaluate ("string (ldml/identity/language/@type)").ToString ();
292 terr_type = nav.Evaluate ("string (ldml/identity/territory/@type)").ToString ();
294 ci.Language = (lang_type == String.Empty ? null : lang_type);
295 ci.Territory = (terr_type == String.Empty ? null : terr_type);
297 if (!LookupLcids (ci, true))
300 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (Lang) + ".xml"));
301 nav = doc.CreateNavigator ();
302 ci.DisplayName = LookupFullName (ci, nav);
305 ci.EnglishName = ci.DisplayName;
307 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
308 nav = doc.CreateNavigator ();
309 ci.EnglishName = LookupFullName (ci, nav);
312 if (ci.Language == Lang) {
313 ci.NativeName = ci.DisplayName;
315 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
316 nav = doc.CreateNavigator ();
317 ci.NativeName = LookupFullName (ci, nav);
320 // Null these out because langs dont have them
321 ci.DateTimeFormatEntry = null;
322 ci.NumberFormatEntry = null;
330 private void ParseLocale (string locale)
334 ci = LookupCulture (locale);
339 if (langs [ci.Language] == null) {
340 if (!ParseLang (ci.Language)) // If we can't parse the lang we cant have the locale
347 private CultureInfoEntry LookupCulture (string locale)
349 XPathDocument doc = GetXPathDocument (Path.Combine ("locales", locale + ".xml"));
350 XPathNavigator nav = doc.CreateNavigator ();
351 CultureInfoEntry ci = new CultureInfoEntry ();
354 // ci.Name = locale; // TODO: Some of these need to be mapped.
356 // First thing we do is get the lang-territory combo, lcid, and full names
357 ci.Language = nav.Evaluate ("string (ldml/identity/language/@type)").ToString ();
358 ci.Territory = nav.Evaluate ("string (ldml/identity/territory/@type)").ToString ();
360 if (!LookupLcids (ci, false))
365 * Locale generation is done in six steps, first we
366 * read the root file which is the base invariant data
367 * then the supplemental root data,
368 * then the language file, the supplemental languages
369 * file then the locale file, then the supplemental
370 * locale file. Values in each descending file can
371 * overwrite previous values.
373 doc = GetXPathDocument (Path.Combine ("langs", "root.xml"));
374 nav = doc.CreateNavigator ();
377 doc = GetXPathDocument (Path.Combine ("supp", "root.xml"));
378 nav = doc.CreateNavigator ();
381 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (ci.Language) + ".xml"));
382 nav = doc.CreateNavigator ();
385 supp = Path.Combine ("supp", ci.Language + ".xml");
386 if (File.Exists (supp)) {
387 doc = GetXPathDocument (supp);
388 nav = doc.CreateNavigator ();
392 doc = GetXPathDocument (Path.Combine ("locales", locale + ".xml"));
393 nav = doc.CreateNavigator ();
396 supp = Path.Combine ("supp", locale + ".xml");
397 if (File.Exists (supp)) {
398 doc = GetXPathDocument (supp);
399 nav = doc.CreateNavigator ();
406 private void Lookup (XPathNavigator nav, CultureInfoEntry ci)
408 LookupDateTimeInfo (nav, ci);
409 LookupNumberInfo (nav, ci);
412 private void LookupNames (CultureInfoEntry ci)
414 XPathDocument doc = GetXPathDocument (Path.Combine ("langs", GetShortName (Lang) + ".xml"));
415 XPathNavigator nav = doc.CreateNavigator ();
417 ci.DisplayName = LookupFullName (ci, nav);
420 ci.EnglishName = ci.DisplayName;
422 doc = GetXPathDocument (Path.Combine ("langs", "en.xml"));
423 nav = doc.CreateNavigator ();
424 ci.EnglishName = LookupFullName (ci, nav);
427 if (ci.Language == Lang) {
428 ci.NativeName = ci.DisplayName;
430 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (ci.Language) + ".xml"));
431 nav = doc.CreateNavigator ();
432 ci.NativeName = LookupFullName (ci, nav);
436 private void AddPattern (ArrayList al, string pattern)
438 if (!al.Contains (pattern))
442 private void LookupDateTimeInfo (XPathNavigator nav, CultureInfoEntry ci)
445 * TODO: Does anyone have multiple calendars?
447 XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("ldml/dates/calendars/calendar");
449 while (ni.MoveNext ()) {
450 DateTimeFormatEntry df = ci.DateTimeFormatEntry;
451 string cal_type = ni.Current.GetAttribute ("type", String.Empty);
453 if (cal_type != String.Empty)
454 df.CalendarType = cal_type;
456 XPathNodeIterator ni2 = (XPathNodeIterator) ni.Current.Evaluate ("optionalCalendars/calendar");
457 int opt_cal_count = 0;
458 while (ni2.MoveNext ()) {
460 string greg_type_str;
461 XPathNavigator df_nav = ni2.Current;
462 switch (df_nav.GetAttribute ("type", String.Empty)) {
473 Console.WriteLine ("unknown calendar type: " +
474 df_nav.GetAttribute ("type", String.Empty));
478 greg_type_str = df_nav.GetAttribute ("greg_type", String.Empty);
479 if (greg_type_str != null && greg_type_str != String.Empty) {
480 GregorianCalendarTypes greg_type = (GregorianCalendarTypes)
481 Enum.Parse (typeof (GregorianCalendarTypes), greg_type_str);
482 int greg_type_int = (int) greg_type;
483 type |= greg_type_int;
486 Console.WriteLine ("Setting cal type: {0:X} for {1}", type, ci.Name);
487 ci.CalendarData [opt_cal_count++] = type;
490 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("monthNames/month");
491 while (ni2.MoveNext ()) {
492 if (ni2.CurrentPosition == 1)
493 df.MonthNames.Clear ();
494 df.MonthNames.Add (ni2.Current.Value);
497 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dayNames/day");
498 while (ni2.MoveNext ()) {
499 if (ni2.CurrentPosition == 1)
500 df.DayNames.Clear ();
501 df.DayNames.Add (ni2.Current.Value);
504 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dayAbbr/day");
505 while (ni2.MoveNext ()) {
506 if (ni2.CurrentPosition == 1)
507 df.AbbreviatedDayNames.Clear ();
508 df.AbbreviatedDayNames.Add (ni2.Current.Value);
511 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("monthAbbr/month");
512 while (ni2.MoveNext ()) {
513 if (ni2.CurrentPosition == 1)
514 df.AbbreviatedMonthNames.Clear ();
515 df.AbbreviatedMonthNames.Add (ni2.Current.Value);
518 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dateFormats/dateFormatLength");
519 while (ni2.MoveNext ()) {
520 XPathNavigator df_nav = ni2.Current;
521 XPathNodeIterator p = df_nav.Select ("dateFormat/pattern");
524 value = p.Current.Value;
525 XPathNodeIterator ext = null;
526 switch (df_nav.GetAttribute ("type", String.Empty)) {
529 ParseFullDateFormat (df, value);
533 df.LongDatePattern = value;
534 ext = df_nav.Select ("extraPatterns/pattern");
535 if (ext.MoveNext ()) {
536 df.LongDatePatterns.Clear ();
537 AddPattern (df.LongDatePatterns, df.LongDatePattern);
539 df.LongDatePatterns.Add (ext.Current.Value);
540 } while (ext.MoveNext ());
543 AddPattern (df.LongDatePatterns, df.LongDatePattern);
547 df.ShortDatePattern = value;
548 ext = df_nav.Select ("extraPatterns/pattern");
549 if (ext.MoveNext ()) {
550 df.ShortDatePatterns.Clear ();
551 AddPattern (df.ShortDatePatterns, df.ShortDatePattern);
553 df.ShortDatePatterns.Add (ext.Current.Value);
554 } while (ext.MoveNext ());
557 AddPattern (df.ShortDatePatterns, df.ShortDatePattern);
561 df.YearMonthPattern = value;
565 df.MonthDayPattern = value;
570 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("timeFormats/timeFormatLength");
571 while (ni2.MoveNext ()) {
572 XPathNavigator df_nav = ni2.Current;
573 XPathNodeIterator p = df_nav.Select ("timeFormat/pattern");
576 value = p.Current.Value;
577 XPathNodeIterator ext = null;
578 switch (df_nav.GetAttribute ("type", String.Empty)) {
581 df.LongTimePattern = value.Replace ('a', 't');
582 ext = df_nav.Select ("extraPatterns/pattern");
583 if (ext.MoveNext ()) {
584 df.LongTimePatterns.Clear ();
585 AddPattern (df.LongTimePatterns, df.LongTimePattern);
587 df.LongTimePatterns.Add (ext.Current.Value);
588 } while (ext.MoveNext ());
591 AddPattern (df.LongTimePatterns, df.LongTimePattern);
595 df.ShortTimePattern = value.Replace ('a', 't');
596 ext = df_nav.Select ("extraPatterns/pattern");
597 if (ext.MoveNext ()) {
598 df.ShortTimePatterns.Clear ();
599 AddPattern (df.ShortTimePatterns, df.ShortTimePattern);
601 df.ShortTimePatterns.Add (ext.Current.Value);
602 } while (ext.MoveNext ());
605 AddPattern (df.ShortTimePatterns, df.ShortTimePattern);
610 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dateTimeFormats/dateTimeFormatLength/dateTimeFormat/pattern");
612 df.FullDateTimePattern = String.Format (ni2.Current.ToString (),
613 df.LongTimePattern, df.LongDatePattern);
615 XPathNodeIterator am = ni.Current.SelectChildren ("am", "");
617 df.AMDesignator = am.Current.Value;
618 XPathNodeIterator pm = ni.Current.SelectChildren ("pm", "");
620 df.PMDesignator = pm.Current.Value;
622 string am = (string) ni.Current.Evaluate ("string(am)");
623 string pm = (string) ni.Current.Evaluate ("string(pm)");
625 if (am != String.Empty)
626 df.AMDesignator = am;
627 if (pm != String.Empty)
628 df.PMDesignator = pm;
632 string date_sep = (string) nav.Evaluate ("string(ldml/dates/symbols/dateSeparator)");
633 string time_sep = (string) nav.Evaluate ("string(ldml/dates/symbols/timeSeparator)");
635 if (date_sep != String.Empty)
636 ci.DateTimeFormatEntry.DateSeparator = date_sep;
637 if (time_sep != String.Empty)
638 ci.DateTimeFormatEntry.TimeSeparator = time_sep;
641 private void LookupNumberInfo (XPathNavigator nav, CultureInfoEntry ci)
643 XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("ldml/numbers");
645 while (ni.MoveNext ()) {
646 LookupNumberSymbols (ni.Current, ci);
647 LookupDecimalFormat (ni.Current, ci);
648 LookupPercentFormat (ni.Current, ci);
649 LookupCurrencyFormat (ni.Current, ci);
650 LookupCurrencySymbol (ni.Current, ci);
654 private void LookupDecimalFormat (XPathNavigator nav, CultureInfoEntry ci)
656 string format = (string) nav.Evaluate ("string(decimalFormats/" +
657 "decimalFormatLength/decimalFormat/pattern)");
659 if (format == String.Empty)
662 string [] part_one, part_two;
663 string [] pos_neg = format.Split (new char [1] {';'}, 2);
665 // Most of the patterns are common in positive and negative
666 if (pos_neg.Length == 1)
667 pos_neg = new string [] {pos_neg [0], pos_neg [0]};
669 if (pos_neg.Length == 2) {
671 part_one = pos_neg [0].Split (new char [1] {'.'}, 2);
672 if (part_one.Length == 1)
673 part_one = new string [] {part_one [0], String.Empty};
675 if (part_one.Length == 2) {
676 // assumed same for both positive and negative
677 // decimal digit side
678 ci.NumberFormatEntry.NumberDecimalDigits = 0;
679 for (int i = 0; i < part_one [1].Length; i++) {
680 if (part_one [1][i] == '#') {
681 ci.NumberFormatEntry.NumberDecimalDigits ++;
684 // FIXME: This should be actually done by modifying culture xml files, but too many files to be modified.
685 if (ci.NumberFormatEntry.NumberDecimalDigits > 0)
686 ci.NumberFormatEntry.NumberDecimalDigits --;
688 // decimal grouping side
689 part_two = part_one [0].Split (',');
690 if (part_two.Length > 1) {
691 int len = part_two.Length - 1;
692 ci.NumberFormatEntry.NumberGroupSizes = new int [len];
693 for (int i = 0; i < len; i++) {
694 string pat = part_two [i + 1];
695 ci.NumberFormatEntry.NumberGroupSizes [i] = pat.Length;
698 ci.NumberFormatEntry.NumberGroupSizes = new int [1] { 3 };
701 if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith (")")) {
702 ci.NumberFormatEntry.NumberNegativePattern = 0;
703 } else if (pos_neg [1].StartsWith ("- ")) {
704 ci.NumberFormatEntry.NumberNegativePattern = 2;
705 } else if (pos_neg [1].StartsWith ("-")) {
706 ci.NumberFormatEntry.NumberNegativePattern = 1;
707 } else if (pos_neg [1].EndsWith (" -")) {
708 ci.NumberFormatEntry.NumberNegativePattern = 4;
709 } else if (pos_neg [1].EndsWith ("-")) {
710 ci.NumberFormatEntry.NumberNegativePattern = 3;
712 ci.NumberFormatEntry.NumberNegativePattern = 1;
718 private void LookupPercentFormat (XPathNavigator nav, CultureInfoEntry ci)
720 string format = (string) nav.Evaluate ("string(percentFormats/" +
721 "percentFormatLength/percentFormat/pattern)");
723 if (format == String.Empty)
726 string [] part_one, part_two;
728 // we don't have percentNegativePattern in CLDR so
729 // the percentNegativePattern are just guesses
730 if (format.StartsWith ("%")) {
731 ci.NumberFormatEntry.PercentPositivePattern = 2;
732 ci.NumberFormatEntry.PercentNegativePattern = 2;
733 format = format.Substring (1);
734 } else if (format.EndsWith (" %")) {
735 ci.NumberFormatEntry.PercentPositivePattern = 0;
736 ci.NumberFormatEntry.PercentNegativePattern = 0;
737 format = format.Substring (0, format.Length - 2);
738 } else if (format.EndsWith ("%")) {
739 ci.NumberFormatEntry.PercentPositivePattern = 1;
740 ci.NumberFormatEntry.PercentNegativePattern = 1;
741 format = format.Substring (0, format.Length - 1);
743 ci.NumberFormatEntry.PercentPositivePattern = 0;
744 ci.NumberFormatEntry.PercentNegativePattern = 0;
747 part_one = format.Split (new char [1] {'.'}, 2);
748 if (part_one.Length == 2) {
749 // assumed same for both positive and negative
750 // decimal digit side
751 ci.NumberFormatEntry.PercentDecimalDigits = 0;
752 for (int i = 0; i < part_one [1].Length; i++) {
753 if (part_one [1][i] == '#')
754 ci.NumberFormatEntry.PercentDecimalDigits++;
760 if (part_one.Length > 0) {
761 // percent grouping side
762 part_two = part_one [0].Split (',');
763 if (part_two.Length > 1) {
764 int len = part_two.Length - 1;
765 ci.NumberFormatEntry.PercentGroupSizes = new int [len];
766 for (int i = 0; i < len; i++) {
767 string pat = part_two [i + 1];
768 if (pat [pat.Length -1] == '0')
769 ci.NumberFormatEntry.PercentDecimalDigits = pat.Length - 1;
770 ci.NumberFormatEntry.PercentGroupSizes [i] = pat.Length;
773 ci.NumberFormatEntry.PercentGroupSizes = new int [1] { 3 };
774 ci.NumberFormatEntry.PercentDecimalDigits = 2;
779 private void LookupCurrencyFormat (XPathNavigator nav, CultureInfoEntry ci)
781 string format = (string) nav.Evaluate ("string(currencyFormats/" +
782 "currencyFormatLength/currencyFormat/pattern)");
784 if (format == String.Empty)
787 string [] part_one, part_two;
788 string [] pos_neg = format.Split (new char [1] {';'}, 2);
790 // Most of the patterns are common in positive and negative
791 if (pos_neg.Length == 1)
792 pos_neg = new string [] {pos_neg [0], pos_neg [0]};
794 if (pos_neg.Length == 2) {
795 part_one = pos_neg [0].Split (new char [1] {'.'}, 2);
796 if (part_one.Length == 1)
797 part_one = new string [] {part_one [0], String.Empty};
798 if (part_one.Length == 2) {
799 // assumed same for both positive and negative
800 // decimal digit side
801 ci.NumberFormatEntry.CurrencyDecimalDigits = 0;
802 for (int i = 0; i < part_one [1].Length; i++) {
803 if (part_one [1][i] == '0')
804 ci.NumberFormatEntry.CurrencyDecimalDigits++;
809 // decimal grouping side
810 part_two = part_one [0].Split (',');
811 if (part_two.Length > 1) {
812 int len = part_two.Length - 1;
813 ci.NumberFormatEntry.CurrencyGroupSizes = new int [len];
814 for (int i = 0; i < len; i++) {
815 string pat = part_two [i + 1];
816 ci.NumberFormatEntry.CurrencyGroupSizes [i] = pat.Length;
819 ci.NumberFormatEntry.CurrencyGroupSizes = new int [1] { 3 };
822 if (pos_neg [1].StartsWith ("(\u00a4 ") && pos_neg [1].EndsWith (")")) {
823 ci.NumberFormatEntry.CurrencyNegativePattern = 14;
824 } else if (pos_neg [1].StartsWith ("(\u00a4") && pos_neg [1].EndsWith (")")) {
825 ci.NumberFormatEntry.CurrencyNegativePattern = 0;
826 } else if (pos_neg [1].StartsWith ("\u00a4 ") && pos_neg [1].EndsWith ("-")) {
827 ci.NumberFormatEntry.CurrencyNegativePattern = 11;
828 } else if (pos_neg [1].StartsWith ("\u00a4") && pos_neg [1].EndsWith ("-")) {
829 ci.NumberFormatEntry.CurrencyNegativePattern = 3;
830 } else if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith (" \u00a4")) {
831 ci.NumberFormatEntry.CurrencyNegativePattern = 15;
832 } else if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith ("\u00a4")) {
833 ci.NumberFormatEntry.CurrencyNegativePattern = 4;
834 } else if (pos_neg [1].StartsWith ("-") && pos_neg [1].EndsWith (" \u00a4")) {
835 ci.NumberFormatEntry.CurrencyNegativePattern = 8;
836 } else if (pos_neg [1].StartsWith ("-") && pos_neg [1].EndsWith ("\u00a4")) {
837 ci.NumberFormatEntry.CurrencyNegativePattern = 5;
838 } else if (pos_neg [1].StartsWith ("-\u00a4 ")) {
839 ci.NumberFormatEntry.CurrencyNegativePattern = 9;
840 } else if (pos_neg [1].StartsWith ("-\u00a4")) {
841 ci.NumberFormatEntry.CurrencyNegativePattern = 1;
842 } else if (pos_neg [1].StartsWith ("\u00a4 -")) {
843 ci.NumberFormatEntry.CurrencyNegativePattern = 12;
844 } else if (pos_neg [1].StartsWith ("\u00a4-")) {
845 ci.NumberFormatEntry.CurrencyNegativePattern = 2;
846 } else if (pos_neg [1].EndsWith (" \u00a4-")) {
847 ci.NumberFormatEntry.CurrencyNegativePattern = 10;
848 } else if (pos_neg [1].EndsWith ("\u00a4-")) {
849 ci.NumberFormatEntry.CurrencyNegativePattern = 7;
850 } else if (pos_neg [1].EndsWith ("- \u00a4")) {
851 ci.NumberFormatEntry.CurrencyNegativePattern = 13;
852 } else if (pos_neg [1].EndsWith ("-\u00a4")) {
853 ci.NumberFormatEntry.CurrencyNegativePattern = 6;
855 ci.NumberFormatEntry.CurrencyNegativePattern = 0;
858 if (pos_neg [0].StartsWith ("\u00a4 ")) {
859 ci.NumberFormatEntry.CurrencyPositivePattern = 2;
860 } else if (pos_neg [0].StartsWith ("\u00a4")) {
861 ci.NumberFormatEntry.CurrencyPositivePattern = 0;
862 } else if (pos_neg [0].EndsWith (" \u00a4")) {
863 ci.NumberFormatEntry.CurrencyPositivePattern = 3;
864 } else if (pos_neg [0].EndsWith ("\u00a4")) {
865 ci.NumberFormatEntry.CurrencyPositivePattern = 1;
867 ci.NumberFormatEntry.CurrencyPositivePattern = 0;
873 private void LookupNumberSymbols (XPathNavigator nav, CultureInfoEntry ci)
875 string dec = (string) nav.Evaluate ("string(symbols/decimal)");
876 string group = (string) nav.Evaluate ("string(symbols/group)");
877 string percent = (string) nav.Evaluate ("string(symbols/percentSign)");
878 string positive = (string) nav.Evaluate ("string(symbols/plusSign)");
879 string negative = (string) nav.Evaluate ("string(symbols/minusSign)");
880 string per_mille = (string) nav.Evaluate ("string(symbols/perMille)");
881 string infinity = (string) nav.Evaluate ("string(symbols/infinity)");
882 string nan = (string) nav.Evaluate ("string(symbols/nan)");
884 if (dec != String.Empty) {
885 ci.NumberFormatEntry.NumberDecimalSeparator = dec;
886 ci.NumberFormatEntry.PercentDecimalSeparator = dec;
887 ci.NumberFormatEntry.CurrencyDecimalSeparator = dec;
890 if (group != String.Empty) {
891 ci.NumberFormatEntry.NumberGroupSeparator = group;
892 ci.NumberFormatEntry.PercentGroupSeparator = group;
893 ci.NumberFormatEntry.CurrencyGroupSeparator = group;
896 if (percent != String.Empty)
897 ci.NumberFormatEntry.PercentSymbol = percent;
898 if (positive != String.Empty)
899 ci.NumberFormatEntry.PositiveSign = positive;
900 if (negative != String.Empty)
901 ci.NumberFormatEntry.NegativeSign = negative;
902 if (per_mille != String.Empty)
903 ci.NumberFormatEntry.PerMilleSymbol = per_mille;
904 if (infinity != String.Empty)
905 ci.NumberFormatEntry.PositiveInfinitySymbol = infinity;
906 if (nan != String.Empty)
907 ci.NumberFormatEntry.NaNSymbol = nan;
910 private void LookupCurrencySymbol (XPathNavigator nav, CultureInfoEntry ci)
912 string type = currency_types [ci.Territory] as string;
915 Console.WriteLine ("no currency type for: " + ci.Territory);
919 string cur = (string) nav.Evaluate ("string(currencies/currency [@type='" +
920 type + "']/symbol)");
922 if (cur != String.Empty)
923 ci.NumberFormatEntry.CurrencySymbol = cur;
926 private bool LookupLcids (CultureInfoEntry ci, bool lang)
928 XPathDocument doc = GetXPathDocument ("lcids.xml");
929 XPathNavigator nav = doc.CreateNavigator ();
930 string name = ci.Name;
931 // Language name does not always consist of locale name.
932 // (for zh-* it must be either zh-CHS or zh-CHT)
933 string langName = ci.Language;
935 // if (ci.Territory != null)
936 // name += "-" + ci.Territory;
938 XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("lcids/lcid[@name='"
939 + (lang ? langName : name) + "']");
940 if (!ni.MoveNext ()) {
941 Console.WriteLine ("no lcid found for: {0} ({1}/{2})", name, ci.Language, ci.Territory);
944 if (ci.Territory != null) {
945 file = Path.Combine ("locales", ci.Language + "_" + ci.Territory + ".xml");
947 Console.WriteLine ("deleting file: " + file);
953 string id = ni.Current.GetAttribute ("id", String.Empty);
954 string parent = ni.Current.GetAttribute ("parent", String.Empty);
955 string specific = ni.Current.GetAttribute ("specific", String.Empty);
956 string iso2 = ni.Current.GetAttribute ("iso2", String.Empty);
957 string iso3 = ni.Current.GetAttribute ("iso3", String.Empty);
958 string win = ni.Current.GetAttribute ("win", String.Empty);
959 string icu = ni.Current.GetAttribute ("icu_name", String.Empty);
961 // lcids are in 0x<hex> format
963 ci.ParentLcid = parent;
964 ci.SpecificLcid = specific;
970 ci.TextInfoEntry = new TextInfoEntry (int.Parse (id.Substring (2), NumberStyles.HexNumber), GetXPathDocument ("textinfo.xml"));
975 private string LookupFullName (CultureInfoEntry ci, XPathNavigator nav)
977 string pre = "ldml/localeDisplayNames/";
980 ret = (string) nav.Evaluate ("string("+
981 pre + "languages/language[@type='" + GetShortName (ci.Language) + "'])");
983 if (ci.Territory == null)
985 ret += " (" + (string) nav.Evaluate ("string("+
986 pre + "territories/territory[@type='" + ci.Territory + "'])") + ")";
991 private void LookupRegions ()
993 XPathDocument doc = GetXPathDocument ("supplementalData.xml");
994 XPathNavigator nav = doc.CreateNavigator ();
995 XPathNodeIterator ni = nav.Select ("supplementalData/currencyData/region");
996 while (ni.MoveNext ()) {
997 string territory = (string) ni.Current.GetAttribute ("iso3166", String.Empty);
998 string currency = (string) ni.Current.Evaluate ("string(currency/@iso4217)");
999 RegionInfoEntry region = new RegionInfoEntry ();
1000 region.ISO2Name = territory.ToUpper ();
1001 region.ISOCurrencySymbol = currency;
1002 regions [territory] = region;
1005 doc = GetXPathDocument ("langs/en.xml");
1006 nav = doc.CreateNavigator ();
1007 ni = nav.Select ("/ldml/localeDisplayNames/territories/territory");
1008 while (ni.MoveNext ()) {
1009 RegionInfoEntry r = (RegionInfoEntry)
1010 regions [ni.Current.GetAttribute ("type", "")];
1013 r.EnglishName = ni.Current.Value;
1016 Hashtable curNames = new Hashtable ();
1017 ni = nav.Select ("/ldml/numbers/currencies/currency");
1018 while (ni.MoveNext ())
1019 curNames [ni.Current.GetAttribute ("type", "")] =
1020 ni.Current.Evaluate ("string (displayName)");
1022 foreach (RegionInfoEntry r in regions.Values)
1023 r.CurrencyEnglishName =
1024 (string) curNames [r.ISOCurrencySymbol];
1027 private void LookupCurrencyTypes ()
1029 XPathDocument doc = GetXPathDocument ("supplementalData.xml");
1030 XPathNavigator nav = doc.CreateNavigator ();
1032 currency_types = new Hashtable ();
1034 XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("supplementalData/currencyData/region");
1035 while (ni.MoveNext ()) {
1036 string territory = (string) ni.Current.GetAttribute ("iso3166", String.Empty);
1037 string currency = (string) ni.Current.Evaluate ("string(currency/@iso4217)");
1038 currency_types [territory] = currency;
1042 static string control_chars = "ghmsftz";
1044 // HACK: We are trying to build year_month and month_day patterns from the full pattern.
1045 private void ParseFullDateFormat (DateTimeFormatEntry df, string full)
1048 string month_day = String.Empty;
1049 string year_month = String.Empty;
1050 bool in_month_data = false;
1051 bool in_year_data = false;
1054 bool inquote = false;
1056 for (int i = 0; i < full.Length; i++) {
1058 if (!inquote && c == 'M') {
1061 in_year_data = true;
1062 in_month_data = true;
1063 month_end = month_day.Length;
1064 year_end = year_month.Length;
1065 } else if (!inquote && Char.ToLower (c) == 'd') {
1067 in_month_data = true;
1068 in_year_data = false;
1069 month_end = month_day.Length;
1070 } else if (!inquote && Char.ToLower (c) == 'y') {
1072 in_year_data = true;
1073 in_month_data = false;
1074 year_end = year_month.Length;
1075 } else if (!inquote && control_chars.IndexOf (Char.ToLower (c)) >= 0) {
1076 in_year_data = false;
1077 in_month_data = false;
1078 } else if (in_year_data || in_month_data) {
1090 if (month_day != String.Empty) {
1091 month_day = month_day.Substring (0, month_end);
1092 df.MonthDayPattern = month_day;
1094 if (year_month != String.Empty) {
1095 year_month = year_month.Substring (0, year_end);
1096 df.YearMonthPattern = year_month;
1100 private class LcidComparer : IComparer {
1102 public int Compare (object a, object b)
1104 CultureInfoEntry aa = (CultureInfoEntry) a;
1105 CultureInfoEntry bb = (CultureInfoEntry) b;
1107 return aa.Lcid.CompareTo (bb.Lcid);
1111 private class NameComparer : IComparer {
1113 public int Compare (object a, object b)
1115 CultureInfoEntry aa = (CultureInfoEntry) a;
1116 CultureInfoEntry bb = (CultureInfoEntry) b;
1118 return aa.Name.ToLower ().CompareTo (bb.Name.ToLower ());
1122 class RegionComparer : IComparer
1124 public static RegionComparer Instance = new RegionComparer ();
1126 public int Compare (object o1, object o2)
1128 RegionInfoEntry r1 = (RegionInfoEntry) o1;
1129 RegionInfoEntry r2 = (RegionInfoEntry) o2;
1130 return String.CompareOrdinal (
1131 r1.ISO2Name, r2.ISO2Name);
1137 public RegionLCIDMap (int lcid, int regionId)
1140 RegionId = regionId;
1144 public int RegionId;