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 private XPathDocument lcids_doc;
54 // The lang is the language that display names will be displayed in
64 public string Locales {
65 get { return locales; }
66 set { locales = value; }
69 public string HeaderFileName {
71 if (header_name == null)
72 return "culture-info-tables.h";
75 set { header_name = value; }
80 lcids_doc = GetXPathDocument ("lcids.xml");
82 Regex locales_regex = null;
84 locales_regex = new Regex (Locales);
86 langs = new Hashtable ();
87 cultures = new ArrayList ();
88 regions = new Hashtable ();
92 LookupCurrencyTypes ();
94 foreach (string file in Directory.GetFiles ("locales", "*.xml")) {
95 string fn = Path.GetFileNameWithoutExtension (file);
97 continue; // see bug #75499
98 if (locales_regex == null || locales_regex.IsMatch (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
110 foreach (CultureInfoEntry e in cultures) {
111 if (e.Name == "zh-CHS") {
113 CultureInfoEntry.ShallowCopy (e);
114 t.Language = "zh-CHT";
115 LookupLcids (t, true);
121 ArrayList regionList = new ArrayList (regions.Values);
122 regionList.Sort (RegionComparer.Instance);
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);
131 int start = e.Name.IndexOf ('-') + 1;
134 for (idx = start; idx < e.Name.Length; idx++)
135 if (!Char.IsLetter (e.Name [idx]))
138 Console.Error.WriteLine ("Culture {0} {1} is not mappable to Region.", e.Lcid, e.Name);
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) {
149 Console.Error.WriteLine ("No definition for region {0}", name);
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";
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 ();
177 int count = cultures.Count;
178 for (int i = 0; i < count; i++) {
179 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
180 if (ci.DateTimeFormatEntry == null)
182 ci.DateTimeFormatEntry.AppendTableRow (builder);
183 ci.DateTimeFormatEntry.Row = row++;
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 ();
195 for (int i=0; i < count; i++) {
196 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
197 if (ci.NumberFormatEntry == null)
199 ci.NumberFormatEntry.AppendTableRow (builder);
200 ci.NumberFormatEntry.Row = row++;
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 ();
212 for (int i = 0; i < count; i++) {
213 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
214 ci.AppendTableRow (builder);
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 + "}");
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 ();
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 ();
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;
278 xtr = new XmlTextReader (path);
279 xtr.XmlResolver = null;
280 return new XPathDocument (xtr);
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 = new CultureInfoEntry ();
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))
310 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (Lang) + ".xml"));
311 nav = doc.CreateNavigator ();
312 ci.DisplayName = LookupFullName (ci, nav);
315 ci.EnglishName = ci.DisplayName;
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;
325 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
326 nav = doc.CreateNavigator ();
327 ci.NativeName = LookupFullName (ci, nav);
330 // Null these out because langs dont have them
331 ci.DateTimeFormatEntry = null;
332 ci.NumberFormatEntry = null;
340 private void ParseLocale (string locale)
344 ci = LookupCulture (locale);
349 if (langs [GetLanguageFixed (ci)] == null) {
350 if (!ParseLang (GetLanguageFixed (ci))) // If we can't parse the lang we cant have the locale
357 private CultureInfoEntry LookupCulture (string locale)
359 string path = Path.Combine ("locales", locale + ".xml");
360 if (!File.Exists (path))
362 XPathDocument doc = GetXPathDocument (path);
363 XPathNavigator nav = doc.CreateNavigator ();
364 CultureInfoEntry ci = new CultureInfoEntry ();
367 // ci.Name = locale; // TODO: Some of these need to be mapped.
369 // First thing we do is get the lang-territory combo, lcid, and full names
370 ci.Language = nav.Evaluate ("string (ldml/identity/language/@type)").ToString ();
371 ci.Territory = nav.Evaluate ("string (ldml/identity/territory/@type)").ToString ();
373 if (!LookupLcids (ci, false))
378 * Locale generation is done in six steps, first we
379 * read the root file which is the base invariant data
380 * then the supplemental root data,
381 * then the language file, the supplemental languages
382 * file then the locale file, then the supplemental
383 * locale file. Values in each descending file can
384 * overwrite previous values.
386 doc = GetXPathDocument (Path.Combine ("langs", "root.xml"));
387 nav = doc.CreateNavigator ();
390 doc = GetXPathDocument (Path.Combine ("supp", "root.xml"));
391 nav = doc.CreateNavigator ();
394 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (GetLanguageFixed (ci)) + ".xml"));
395 nav = doc.CreateNavigator ();
398 supp = Path.Combine ("supp", GetLanguageFixed (ci) + ".xml");
399 if (File.Exists (supp)) {
400 doc = GetXPathDocument (supp);
401 nav = doc.CreateNavigator ();
405 doc = GetXPathDocument (Path.Combine ("locales", locale + ".xml"));
406 nav = doc.CreateNavigator ();
409 supp = Path.Combine ("supp", locale + ".xml");
410 if (File.Exists (supp)) {
411 doc = GetXPathDocument (supp);
412 nav = doc.CreateNavigator ();
419 private void Lookup (XPathNavigator nav, CultureInfoEntry ci)
421 LookupDateTimeInfo (nav, ci);
422 LookupNumberInfo (nav, ci);
425 private string GetLanguageFixed (CultureInfoEntry ci)
427 // This is a hack, but without it nb-NO and nn-NO won't work.
428 if (ci.Territory == "NO") {
429 switch (ci.Language) {
438 private void LookupNames (CultureInfoEntry ci)
440 XPathDocument doc = GetXPathDocument (Path.Combine ("langs", GetShortName (Lang) + ".xml"));
441 XPathNavigator nav = doc.CreateNavigator ();
443 ci.DisplayName = LookupFullName (ci, nav);
446 ci.EnglishName = ci.DisplayName;
448 doc = GetXPathDocument (Path.Combine ("langs", "en.xml"));
449 nav = doc.CreateNavigator ();
450 ci.EnglishName = LookupFullName (ci, nav);
453 if (ci.Language == Lang) {
454 ci.NativeName = ci.DisplayName;
456 // FIXME: We use ci.Language here.
457 // This is nothing more than hack for nb-NO and nn-NO
458 // where Parent of them is nn (not nb or nn).
459 string lang = ci.Language;
460 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
461 nav = doc.CreateNavigator ();
462 ci.NativeName = LookupFullName (ci, nav);
466 private void AddPattern (ArrayList al, string pattern)
468 if (!al.Contains (pattern))
472 private void LookupDateTimeInfo (XPathNavigator nav, CultureInfoEntry ci)
475 * TODO: Does anyone have multiple calendars?
477 XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("ldml/dates/calendars/calendar");
479 while (ni.MoveNext ()) {
480 DateTimeFormatEntry df = ci.DateTimeFormatEntry;
481 string cal_type = ni.Current.GetAttribute ("type", String.Empty);
483 if (cal_type != String.Empty)
484 df.CalendarType = cal_type;
486 XPathNodeIterator ni2 = (XPathNodeIterator) ni.Current.Evaluate ("optionalCalendars/calendar");
487 int opt_cal_count = 0;
488 while (ni2.MoveNext ()) {
490 string greg_type_str;
491 XPathNavigator df_nav = ni2.Current;
492 switch (df_nav.GetAttribute ("type", String.Empty)) {
503 Console.WriteLine ("unknown calendar type: " +
504 df_nav.GetAttribute ("type", String.Empty));
508 greg_type_str = df_nav.GetAttribute ("greg_type", String.Empty);
509 if (greg_type_str != null && greg_type_str != String.Empty) {
510 GregorianCalendarTypes greg_type = (GregorianCalendarTypes)
511 Enum.Parse (typeof (GregorianCalendarTypes), greg_type_str);
512 int greg_type_int = (int) greg_type;
513 type |= greg_type_int;
516 Console.WriteLine ("Setting cal type: {0:X} for {1}", type, ci.Name);
517 ci.CalendarData [opt_cal_count++] = type;
520 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("monthNames/month");
521 while (ni2.MoveNext ()) {
522 if (ni2.CurrentPosition == 1)
523 df.MonthNames.Clear ();
524 df.MonthNames.Add (ni2.Current.Value);
527 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dayNames/day");
528 while (ni2.MoveNext ()) {
529 if (ni2.CurrentPosition == 1)
530 df.DayNames.Clear ();
531 df.DayNames.Add (ni2.Current.Value);
534 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dayAbbr/day");
535 while (ni2.MoveNext ()) {
536 if (ni2.CurrentPosition == 1)
537 df.AbbreviatedDayNames.Clear ();
538 df.AbbreviatedDayNames.Add (ni2.Current.Value);
541 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("monthAbbr/month");
542 while (ni2.MoveNext ()) {
543 if (ni2.CurrentPosition == 1)
544 df.AbbreviatedMonthNames.Clear ();
545 df.AbbreviatedMonthNames.Add (ni2.Current.Value);
548 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dateFormats/dateFormatLength");
549 while (ni2.MoveNext ()) {
550 XPathNavigator df_nav = ni2.Current;
551 XPathNodeIterator p = df_nav.Select ("dateFormat/pattern");
554 value = p.Current.Value;
555 XPathNodeIterator ext = null;
556 switch (df_nav.GetAttribute ("type", String.Empty)) {
559 ParseFullDateFormat (df, value);
563 df.LongDatePattern = value;
564 ext = df_nav.Select ("extraPatterns/pattern");
565 if (ext.MoveNext ()) {
566 df.LongDatePatterns.Clear ();
567 AddPattern (df.LongDatePatterns, df.LongDatePattern);
569 df.LongDatePatterns.Add (ext.Current.Value);
570 } while (ext.MoveNext ());
573 AddPattern (df.LongDatePatterns, df.LongDatePattern);
577 df.ShortDatePattern = value;
578 ext = df_nav.Select ("extraPatterns/pattern");
579 if (ext.MoveNext ()) {
580 df.ShortDatePatterns.Clear ();
581 AddPattern (df.ShortDatePatterns, df.ShortDatePattern);
583 df.ShortDatePatterns.Add (ext.Current.Value);
584 } while (ext.MoveNext ());
587 AddPattern (df.ShortDatePatterns, df.ShortDatePattern);
591 df.YearMonthPattern = value;
595 df.MonthDayPattern = value;
600 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("timeFormats/timeFormatLength");
601 while (ni2.MoveNext ()) {
602 XPathNavigator df_nav = ni2.Current;
603 XPathNodeIterator p = df_nav.Select ("timeFormat/pattern");
606 value = p.Current.Value;
607 XPathNodeIterator ext = null;
608 switch (df_nav.GetAttribute ("type", String.Empty)) {
611 df.LongTimePattern = value.Replace ('a', 't');
612 ext = df_nav.Select ("extraPatterns/pattern");
613 if (ext.MoveNext ()) {
614 df.LongTimePatterns.Clear ();
615 AddPattern (df.LongTimePatterns, df.LongTimePattern);
617 df.LongTimePatterns.Add (ext.Current.Value);
618 } while (ext.MoveNext ());
621 AddPattern (df.LongTimePatterns, df.LongTimePattern);
625 df.ShortTimePattern = value.Replace ('a', 't');
626 ext = df_nav.Select ("extraPatterns/pattern");
627 if (ext.MoveNext ()) {
628 df.ShortTimePatterns.Clear ();
629 AddPattern (df.ShortTimePatterns, df.ShortTimePattern);
631 df.ShortTimePatterns.Add (ext.Current.Value);
632 } while (ext.MoveNext ());
635 AddPattern (df.ShortTimePatterns, df.ShortTimePattern);
640 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dateTimeFormats/dateTimeFormatLength/dateTimeFormat/pattern");
642 df.FullDateTimePattern = String.Format (ni2.Current.ToString (),
643 df.LongTimePattern, df.LongDatePattern);
645 XPathNodeIterator am = ni.Current.SelectChildren ("am", "");
647 df.AMDesignator = am.Current.Value;
648 XPathNodeIterator pm = ni.Current.SelectChildren ("pm", "");
650 df.PMDesignator = pm.Current.Value;
652 string am = (string) ni.Current.Evaluate ("string(am)");
653 string pm = (string) ni.Current.Evaluate ("string(pm)");
655 if (am != String.Empty)
656 df.AMDesignator = am;
657 if (pm != String.Empty)
658 df.PMDesignator = pm;
662 string date_sep = (string) nav.Evaluate ("string(ldml/dates/symbols/dateSeparator)");
663 string time_sep = (string) nav.Evaluate ("string(ldml/dates/symbols/timeSeparator)");
665 if (date_sep != String.Empty)
666 ci.DateTimeFormatEntry.DateSeparator = date_sep;
667 if (time_sep != String.Empty)
668 ci.DateTimeFormatEntry.TimeSeparator = time_sep;
671 private void LookupNumberInfo (XPathNavigator nav, CultureInfoEntry ci)
673 XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("ldml/numbers");
675 while (ni.MoveNext ()) {
676 LookupNumberSymbols (ni.Current, ci);
677 LookupDecimalFormat (ni.Current, ci);
678 LookupPercentFormat (ni.Current, ci);
679 LookupCurrencyFormat (ni.Current, ci);
680 LookupCurrencySymbol (ni.Current, ci);
684 private void LookupDecimalFormat (XPathNavigator nav, CultureInfoEntry ci)
686 string format = (string) nav.Evaluate ("string(decimalFormats/" +
687 "decimalFormatLength/decimalFormat/pattern)");
689 if (format == String.Empty)
692 string [] part_one, part_two;
693 string [] pos_neg = format.Split (new char [1] {';'}, 2);
695 // Most of the patterns are common in positive and negative
696 if (pos_neg.Length == 1)
697 pos_neg = new string [] {pos_neg [0], pos_neg [0]};
699 if (pos_neg.Length == 2) {
701 part_one = pos_neg [0].Split (new char [1] {'.'}, 2);
702 if (part_one.Length == 1)
703 part_one = new string [] {part_one [0], String.Empty};
705 if (part_one.Length == 2) {
706 // assumed same for both positive and negative
707 // decimal digit side
708 ci.NumberFormatEntry.NumberDecimalDigits = 0;
709 for (int i = 0; i < part_one [1].Length; i++) {
710 if (part_one [1][i] == '#') {
711 ci.NumberFormatEntry.NumberDecimalDigits ++;
714 // FIXME: This should be actually done by modifying culture xml files, but too many files to be modified.
715 if (ci.NumberFormatEntry.NumberDecimalDigits > 0)
716 ci.NumberFormatEntry.NumberDecimalDigits --;
718 // decimal grouping side
719 part_two = part_one [0].Split (',');
720 if (part_two.Length > 1) {
721 int len = part_two.Length - 1;
722 ci.NumberFormatEntry.NumberGroupSizes = new int [len];
723 for (int i = 0; i < len; i++) {
724 string pat = part_two [i + 1];
725 ci.NumberFormatEntry.NumberGroupSizes [i] = pat.Length;
728 ci.NumberFormatEntry.NumberGroupSizes = new int [1] { 3 };
731 if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith (")")) {
732 ci.NumberFormatEntry.NumberNegativePattern = 0;
733 } else if (pos_neg [1].StartsWith ("- ")) {
734 ci.NumberFormatEntry.NumberNegativePattern = 2;
735 } else if (pos_neg [1].StartsWith ("-")) {
736 ci.NumberFormatEntry.NumberNegativePattern = 1;
737 } else if (pos_neg [1].EndsWith (" -")) {
738 ci.NumberFormatEntry.NumberNegativePattern = 4;
739 } else if (pos_neg [1].EndsWith ("-")) {
740 ci.NumberFormatEntry.NumberNegativePattern = 3;
742 ci.NumberFormatEntry.NumberNegativePattern = 1;
748 private void LookupPercentFormat (XPathNavigator nav, CultureInfoEntry ci)
750 string format = (string) nav.Evaluate ("string(percentFormats/" +
751 "percentFormatLength/percentFormat/pattern)");
753 if (format == String.Empty)
756 string [] part_one, part_two;
758 // we don't have percentNegativePattern in CLDR so
759 // the percentNegativePattern are just guesses
760 if (format.StartsWith ("%")) {
761 ci.NumberFormatEntry.PercentPositivePattern = 2;
762 ci.NumberFormatEntry.PercentNegativePattern = 2;
763 format = format.Substring (1);
764 } else if (format.EndsWith (" %")) {
765 ci.NumberFormatEntry.PercentPositivePattern = 0;
766 ci.NumberFormatEntry.PercentNegativePattern = 0;
767 format = format.Substring (0, format.Length - 2);
768 } else if (format.EndsWith ("%")) {
769 ci.NumberFormatEntry.PercentPositivePattern = 1;
770 ci.NumberFormatEntry.PercentNegativePattern = 1;
771 format = format.Substring (0, format.Length - 1);
773 ci.NumberFormatEntry.PercentPositivePattern = 0;
774 ci.NumberFormatEntry.PercentNegativePattern = 0;
777 part_one = format.Split (new char [1] {'.'}, 2);
778 if (part_one.Length == 2) {
779 // assumed same for both positive and negative
780 // decimal digit side
781 ci.NumberFormatEntry.PercentDecimalDigits = 0;
782 for (int i = 0; i < part_one [1].Length; i++) {
783 if (part_one [1][i] == '#')
784 ci.NumberFormatEntry.PercentDecimalDigits++;
790 if (part_one.Length > 0) {
791 // percent grouping side
792 part_two = part_one [0].Split (',');
793 if (part_two.Length > 1) {
794 int len = part_two.Length - 1;
795 ci.NumberFormatEntry.PercentGroupSizes = new int [len];
796 for (int i = 0; i < len; i++) {
797 string pat = part_two [i + 1];
798 if (pat [pat.Length -1] == '0')
799 ci.NumberFormatEntry.PercentDecimalDigits = pat.Length - 1;
800 ci.NumberFormatEntry.PercentGroupSizes [i] = pat.Length;
803 ci.NumberFormatEntry.PercentGroupSizes = new int [1] { 3 };
804 ci.NumberFormatEntry.PercentDecimalDigits = 2;
809 private void LookupCurrencyFormat (XPathNavigator nav, CultureInfoEntry ci)
811 string format = (string) nav.Evaluate ("string(currencyFormats/" +
812 "currencyFormatLength/currencyFormat/pattern)");
814 if (format == String.Empty)
817 string [] part_one, part_two;
818 string [] pos_neg = format.Split (new char [1] {';'}, 2);
820 // Most of the patterns are common in positive and negative
821 if (pos_neg.Length == 1)
822 pos_neg = new string [] {pos_neg [0], pos_neg [0]};
824 if (pos_neg.Length == 2) {
825 part_one = pos_neg [0].Split (new char [1] {'.'}, 2);
826 if (part_one.Length == 1)
827 part_one = new string [] {part_one [0], String.Empty};
828 if (part_one.Length == 2) {
829 // assumed same for both positive and negative
830 // decimal digit side
831 ci.NumberFormatEntry.CurrencyDecimalDigits = 0;
832 for (int i = 0; i < part_one [1].Length; i++) {
833 if (part_one [1][i] == '0')
834 ci.NumberFormatEntry.CurrencyDecimalDigits++;
839 // decimal grouping side
840 part_two = part_one [0].Split (',');
841 if (part_two.Length > 1) {
842 int len = part_two.Length - 1;
843 ci.NumberFormatEntry.CurrencyGroupSizes = new int [len];
844 for (int i = 0; i < len; i++) {
845 string pat = part_two [i + 1];
846 ci.NumberFormatEntry.CurrencyGroupSizes [i] = pat.Length;
849 ci.NumberFormatEntry.CurrencyGroupSizes = new int [1] { 3 };
852 if (pos_neg [1].StartsWith ("(\u00a4 ") && pos_neg [1].EndsWith (")")) {
853 ci.NumberFormatEntry.CurrencyNegativePattern = 14;
854 } else if (pos_neg [1].StartsWith ("(\u00a4") && pos_neg [1].EndsWith (")")) {
855 ci.NumberFormatEntry.CurrencyNegativePattern = 0;
856 } else if (pos_neg [1].StartsWith ("\u00a4 ") && pos_neg [1].EndsWith ("-")) {
857 ci.NumberFormatEntry.CurrencyNegativePattern = 11;
858 } else if (pos_neg [1].StartsWith ("\u00a4") && pos_neg [1].EndsWith ("-")) {
859 ci.NumberFormatEntry.CurrencyNegativePattern = 3;
860 } else if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith (" \u00a4")) {
861 ci.NumberFormatEntry.CurrencyNegativePattern = 15;
862 } else if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith ("\u00a4")) {
863 ci.NumberFormatEntry.CurrencyNegativePattern = 4;
864 } else if (pos_neg [1].StartsWith ("-") && pos_neg [1].EndsWith (" \u00a4")) {
865 ci.NumberFormatEntry.CurrencyNegativePattern = 8;
866 } else if (pos_neg [1].StartsWith ("-") && pos_neg [1].EndsWith ("\u00a4")) {
867 ci.NumberFormatEntry.CurrencyNegativePattern = 5;
868 } else if (pos_neg [1].StartsWith ("-\u00a4 ")) {
869 ci.NumberFormatEntry.CurrencyNegativePattern = 9;
870 } else if (pos_neg [1].StartsWith ("-\u00a4")) {
871 ci.NumberFormatEntry.CurrencyNegativePattern = 1;
872 } else if (pos_neg [1].StartsWith ("\u00a4 -")) {
873 ci.NumberFormatEntry.CurrencyNegativePattern = 12;
874 } else if (pos_neg [1].StartsWith ("\u00a4-")) {
875 ci.NumberFormatEntry.CurrencyNegativePattern = 2;
876 } else if (pos_neg [1].EndsWith (" \u00a4-")) {
877 ci.NumberFormatEntry.CurrencyNegativePattern = 10;
878 } else if (pos_neg [1].EndsWith ("\u00a4-")) {
879 ci.NumberFormatEntry.CurrencyNegativePattern = 7;
880 } else if (pos_neg [1].EndsWith ("- \u00a4")) {
881 ci.NumberFormatEntry.CurrencyNegativePattern = 13;
882 } else if (pos_neg [1].EndsWith ("-\u00a4")) {
883 ci.NumberFormatEntry.CurrencyNegativePattern = 6;
885 ci.NumberFormatEntry.CurrencyNegativePattern = 0;
888 if (pos_neg [0].StartsWith ("\u00a4 ")) {
889 ci.NumberFormatEntry.CurrencyPositivePattern = 2;
890 } else if (pos_neg [0].StartsWith ("\u00a4")) {
891 ci.NumberFormatEntry.CurrencyPositivePattern = 0;
892 } else if (pos_neg [0].EndsWith (" \u00a4")) {
893 ci.NumberFormatEntry.CurrencyPositivePattern = 3;
894 } else if (pos_neg [0].EndsWith ("\u00a4")) {
895 ci.NumberFormatEntry.CurrencyPositivePattern = 1;
897 ci.NumberFormatEntry.CurrencyPositivePattern = 0;
903 private void LookupNumberSymbols (XPathNavigator nav, CultureInfoEntry ci)
905 string dec = (string) nav.Evaluate ("string(symbols/decimal)");
906 string group = (string) nav.Evaluate ("string(symbols/group)");
907 string percent = (string) nav.Evaluate ("string(symbols/percentSign)");
908 string positive = (string) nav.Evaluate ("string(symbols/plusSign)");
909 string negative = (string) nav.Evaluate ("string(symbols/minusSign)");
910 string per_mille = (string) nav.Evaluate ("string(symbols/perMille)");
911 string infinity = (string) nav.Evaluate ("string(symbols/infinity)");
912 string nan = (string) nav.Evaluate ("string(symbols/nan)");
914 if (dec != String.Empty) {
915 ci.NumberFormatEntry.NumberDecimalSeparator = dec;
916 ci.NumberFormatEntry.PercentDecimalSeparator = dec;
917 ci.NumberFormatEntry.CurrencyDecimalSeparator = dec;
920 if (group != String.Empty) {
921 ci.NumberFormatEntry.NumberGroupSeparator = group;
922 ci.NumberFormatEntry.PercentGroupSeparator = group;
923 ci.NumberFormatEntry.CurrencyGroupSeparator = group;
926 if (percent != String.Empty)
927 ci.NumberFormatEntry.PercentSymbol = percent;
928 if (positive != String.Empty)
929 ci.NumberFormatEntry.PositiveSign = positive;
930 if (negative != String.Empty)
931 ci.NumberFormatEntry.NegativeSign = negative;
932 if (per_mille != String.Empty)
933 ci.NumberFormatEntry.PerMilleSymbol = per_mille;
934 if (infinity != String.Empty)
935 ci.NumberFormatEntry.PositiveInfinitySymbol = infinity;
936 if (nan != String.Empty)
937 ci.NumberFormatEntry.NaNSymbol = nan;
940 private void LookupCurrencySymbol (XPathNavigator nav, CultureInfoEntry ci)
942 string type = currency_types [ci.Territory] as string;
945 Console.WriteLine ("no currency type for: " + ci.Territory);
949 string cur = (string) nav.Evaluate ("string(currencies/currency [@type='" +
950 type + "']/symbol)");
952 if (cur != String.Empty)
953 ci.NumberFormatEntry.CurrencySymbol = cur;
956 private bool LookupLcids (CultureInfoEntry ci, bool lang)
958 XPathNavigator nav = lcids_doc.CreateNavigator ();
959 string name = ci.Name;
960 // Language name does not always consist of locale name.
961 // (for zh-* it must be either zh-CHS or zh-CHT)
962 string langName = GetLanguageFixed (ci);
964 // if (ci.Territory != null)
965 // name += "-" + ci.Territory;
967 XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("lcids/lcid[@name='"
968 + (lang ? langName : name) + "']");
969 if (!ni.MoveNext ()) {
970 Console.WriteLine ("no lcid found for: {0} ({1}/{2})", name, ci.Language, ci.Territory);
973 if (ci.Territory != null) {
974 file = Path.Combine ("locales", ci.Language + "_" + ci.Territory + ".xml");
975 Console.WriteLine ("deleting file: " + file);
982 string id = ni.Current.GetAttribute ("id", String.Empty);
983 string parent = ni.Current.GetAttribute ("parent", String.Empty);
984 string specific = ni.Current.GetAttribute ("specific", String.Empty);
985 string iso2 = ni.Current.GetAttribute ("iso2", String.Empty);
986 string iso3 = ni.Current.GetAttribute ("iso3", String.Empty);
987 string win = ni.Current.GetAttribute ("win", String.Empty);
988 string icu = ni.Current.GetAttribute ("icu_name", String.Empty);
990 // lcids are in 0x<hex> format
992 ci.ParentLcid = parent;
993 ci.SpecificLcid = specific;
999 ci.TextInfoEntry = new TextInfoEntry (int.Parse (id.Substring (2), NumberStyles.HexNumber), GetXPathDocument ("textinfo.xml"));
1004 private string LookupFullName (CultureInfoEntry ci, XPathNavigator nav)
1006 string pre = "ldml/localeDisplayNames/";
1009 // FIXME: We use ci.Language here.
1010 // This is nothing more than hack for nb-NO or nn-NO
1011 // where Parent of them is nn (not nb or nn).
1012 ret = (string) nav.Evaluate ("string("+
1013 pre + "languages/language[@type='" + GetShortName (ci.Language) + "'])");
1015 if (ci.Territory == null)
1017 ret += " (" + (string) nav.Evaluate ("string("+
1018 pre + "territories/territory[@type='" + ci.Territory + "'])") + ")";
1023 private void LookupRegions ()
1025 XPathDocument doc = GetXPathDocument ("supplementalData.xml");
1026 XPathNavigator nav = doc.CreateNavigator ();
1027 XPathNodeIterator ni = nav.Select ("supplementalData/currencyData/region");
1028 while (ni.MoveNext ()) {
1029 string territory = (string) ni.Current.GetAttribute ("iso3166", String.Empty);
1030 string currency = (string) ni.Current.Evaluate ("string(currency/@iso4217)");
1031 RegionInfoEntry region = new RegionInfoEntry ();
1032 region.ISO2Name = territory.ToUpper ();
1033 region.ISOCurrencySymbol = currency;
1034 regions [territory] = region;
1037 doc = GetXPathDocument ("langs/en.xml");
1038 nav = doc.CreateNavigator ();
1039 ni = nav.Select ("/ldml/localeDisplayNames/territories/territory");
1040 while (ni.MoveNext ()) {
1041 RegionInfoEntry r = (RegionInfoEntry)
1042 regions [ni.Current.GetAttribute ("type", "")];
1045 r.EnglishName = ni.Current.Value;
1048 Hashtable curNames = new Hashtable ();
1049 ni = nav.Select ("/ldml/numbers/currencies/currency");
1050 while (ni.MoveNext ())
1051 curNames [ni.Current.GetAttribute ("type", "")] =
1052 ni.Current.Evaluate ("string (displayName)");
1054 foreach (RegionInfoEntry r in regions.Values)
1055 r.CurrencyEnglishName =
1056 (string) curNames [r.ISOCurrencySymbol];
1059 private void LookupCurrencyTypes ()
1061 XPathDocument doc = GetXPathDocument ("supplementalData.xml");
1062 XPathNavigator nav = doc.CreateNavigator ();
1064 currency_types = new Hashtable ();
1066 XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("supplementalData/currencyData/region");
1067 while (ni.MoveNext ()) {
1068 string territory = (string) ni.Current.GetAttribute ("iso3166", String.Empty);
1069 string currency = (string) ni.Current.Evaluate ("string(currency/@iso4217)");
1070 currency_types [territory] = currency;
1074 static string control_chars = "ghmsftz";
1076 // HACK: We are trying to build year_month and month_day patterns from the full pattern.
1077 private void ParseFullDateFormat (DateTimeFormatEntry df, string full)
1080 string month_day = String.Empty;
1081 string year_month = String.Empty;
1082 bool in_month_data = false;
1083 bool in_year_data = false;
1086 bool inquote = false;
1088 for (int i = 0; i < full.Length; i++) {
1090 if (!inquote && c == 'M') {
1093 in_year_data = true;
1094 in_month_data = true;
1095 month_end = month_day.Length;
1096 year_end = year_month.Length;
1097 } else if (!inquote && Char.ToLower (c) == 'd') {
1099 in_month_data = true;
1100 in_year_data = false;
1101 month_end = month_day.Length;
1102 } else if (!inquote && Char.ToLower (c) == 'y') {
1104 in_year_data = true;
1105 in_month_data = false;
1106 year_end = year_month.Length;
1107 } else if (!inquote && control_chars.IndexOf (Char.ToLower (c)) >= 0) {
1108 in_year_data = false;
1109 in_month_data = false;
1110 } else if (in_year_data || in_month_data) {
1122 if (month_day != String.Empty) {
1123 month_day = month_day.Substring (0, month_end);
1124 df.MonthDayPattern = month_day;
1126 if (year_month != String.Empty) {
1127 year_month = year_month.Substring (0, year_end);
1128 df.YearMonthPattern = year_month;
1132 private class LcidComparer : IComparer {
1134 public int Compare (object a, object b)
1136 CultureInfoEntry aa = (CultureInfoEntry) a;
1137 CultureInfoEntry bb = (CultureInfoEntry) b;
1139 return aa.Lcid.CompareTo (bb.Lcid);
1143 private class NameComparer : IComparer {
1145 public int Compare (object a, object b)
1147 CultureInfoEntry aa = (CultureInfoEntry) a;
1148 CultureInfoEntry bb = (CultureInfoEntry) b;
1150 return aa.Name.ToLower ().CompareTo (bb.Name.ToLower ());
1154 class RegionComparer : IComparer
1156 public static RegionComparer Instance = new RegionComparer ();
1158 public int Compare (object o1, object o2)
1160 RegionInfoEntry r1 = (RegionInfoEntry) o1;
1161 RegionInfoEntry r2 = (RegionInfoEntry) o2;
1162 return String.CompareOrdinal (
1163 r1.ISO2Name, r2.ISO2Name);
1169 public RegionLCIDMap (int lcid, int regionId)
1172 RegionId = regionId;
1176 public int RegionId;