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
= 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))
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
);
336 private void ParseLocale (string locale
)
340 ci
= LookupCulture (locale
);
345 if (langs
[GetLanguageFixed (ci
)] == null) {
346 if (!ParseLang (GetLanguageFixed (ci
))) // If we can't parse the lang we cant have the locale
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
))
362 XPathDocument doc
= GetXPathDocument (path
);
363 XPathNavigator nav
= doc
.CreateNavigator ();
364 CultureInfoEntry ci
= new CultureInfoEntry ();
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
))
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 ();
391 doc
= GetXPathDocument (Path
.Combine ("supp", "root.xml"));
392 nav
= doc
.CreateNavigator ();
395 doc
= GetXPathDocument (Path
.Combine ("langs", GetShortName (GetLanguageFixed (ci
)) + ".xml"));
396 nav
= doc
.CreateNavigator ();
399 supp
= Path
.Combine ("supp", GetLanguageFixed (ci
) + ".xml");
400 if (File
.Exists (supp
)) {
401 doc
= GetXPathDocument (supp
);
402 nav
= doc
.CreateNavigator ();
406 loc
= Path
.Combine ("locales", locale
+ ".xml");
407 if (File
.Exists (loc
)) {
408 doc
= GetXPathDocument (loc
);
409 nav
= doc
.CreateNavigator ();
413 supp
= Path
.Combine ("supp", locale
+ ".xml");
414 if (File
.Exists (supp
)) {
415 doc
= GetXPathDocument (supp
);
416 nav
= doc
.CreateNavigator ();
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
) {
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
);
450 ci
.EnglishName
= ci
.DisplayName
;
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
;
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
))
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 ()) {
494 string greg_type_str
;
495 XPathNavigator df_nav
= ni2
.Current
;
496 switch (df_nav
.GetAttribute ("type", String
.Empty
)) {
507 Console
.WriteLine ("unknown calendar type: " +
508 df_nav
.GetAttribute ("type", String
.Empty
));
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");
562 value = p
.Current
.Value
;
563 XPathNodeIterator ext
= null;
564 switch (df_nav
.GetAttribute ("type", String
.Empty
)) {
567 ParseFullDateFormat (df
, value);
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
);
577 df
.LongDatePatterns
.Add (ext
.Current
.Value
);
578 } while (ext
.MoveNext ());
581 AddPattern (df
.LongDatePatterns
, df
.LongDatePattern
);
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
);
591 df
.ShortDatePatterns
.Add (ext
.Current
.Value
);
592 } while (ext
.MoveNext ());
595 AddPattern (df
.ShortDatePatterns
, df
.ShortDatePattern
);
599 df
.YearMonthPattern
= value;
603 df
.MonthDayPattern
= value;
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");
614 value = p
.Current
.Value
;
615 XPathNodeIterator ext
= null;
616 switch (df_nav
.GetAttribute ("type", String
.Empty
)) {
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
);
625 df
.LongTimePatterns
.Add (ext
.Current
.Value
);
626 } while (ext
.MoveNext ());
629 AddPattern (df
.LongTimePatterns
, df
.LongTimePattern
);
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
);
639 df
.ShortTimePatterns
.Add (ext
.Current
.Value
);
640 } while (ext
.MoveNext ());
643 AddPattern (df
.ShortTimePatterns
, df
.ShortTimePattern
);
648 ni2
= (XPathNodeIterator
) ni
.Current
.Evaluate ("dateTimeFormats/dateTimeFormatLength/dateTimeFormat/pattern");
650 df
.RawFullDateTimePattern
= ni2
.Current
.ToString ();/*String.Format (ni2.Current.ToString (),
651 df.LongTimePattern, df.LongDatePattern);*/
653 XPathNodeIterator am
= ni
.Current
.SelectChildren ("am", "");
655 df
.AMDesignator
= am
.Current
.Value
;
656 XPathNodeIterator pm
= ni
.Current
.SelectChildren ("pm", "");
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
670 if (ni2
.MoveNext ()) {
671 XPathNavigator weekday_nav
= ni2
.Current
;
672 switch (weekday_nav
.GetAttribute ("day", String
.Empty
)) {
674 df
.FirstDayOfWeek
= 0;
677 df
.FirstDayOfWeek
= 1;
680 df
.FirstDayOfWeek
= 2;
683 df
.FirstDayOfWeek
= 3;
686 df
.FirstDayOfWeek
= 4;
689 df
.FirstDayOfWeek
= 5;
692 df
.FirstDayOfWeek
= 6;
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
)
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
++;
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
;
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;
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
)
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);
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
++;
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
;
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
)
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
++;
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
;
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;
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;
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;
981 Console
.WriteLine ("no currency type for: " + ci
.Territory
);
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
);
1009 if (ci
.Territory
!= null) {
1010 file
= Path
.Combine ("locales", ci
.Language
+ "_" + ci
.Territory
+ ".xml");
1011 Console
.WriteLine ("deleting file: " + file
);
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
1028 ci
.ParentLcid
= parent
;
1029 ci
.SpecificLcid
= specific
;
1035 ci
.TextInfoEntry
= new TextInfoEntry (int.Parse (id
.Substring (2), NumberStyles
.HexNumber
), GetXPathDocument ("textinfo.xml"));
1040 private string LookupFullName (CultureInfoEntry ci
, XPathNavigator nav
)
1042 string pre
= "ldml/localeDisplayNames/";
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)
1053 ret
+= " (" + (string) nav
.Evaluate ("string("+
1054 pre
+ "territories/territory[@type='" + ci
.Territory
+ "'])") + ")";
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", "")];
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
++) {
1127 if (!inquote
&& c
== 'M') {
1130 in_year_data
= true;
1131 in_month_data
= true;
1132 if (month_start
== 0)
1135 year_end
= year_month
.Length
;
1136 } else if (!inquote
&& Char
.ToLower (c
) == 'd') {
1138 in_month_data
= true;
1139 in_year_data
= false;
1143 } else if (!inquote
&& Char
.ToLower (c
) == 'y') {
1145 in_year_data
= true;
1146 in_month_data
= false;
1147 if (year_start
== 0)
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
) {
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
)
1178 p
= p
.Trim ().TrimEnd (',');
1179 idx
= p
.LastIndexOf ("' de '"); // spanish dates
1181 p
= p
.Substring (0, idx
);
1182 idx
= p
.LastIndexOf ("' ta '"); // finnish
1184 p
= p
.Substring (0, idx
);
1185 idx
= p
.LastIndexOf ("'ren'"); // euskara
1187 p
= p
.Replace ("'ren'", "").Trim ();
1188 idx
= p
.LastIndexOf ("'a'"); // estonian
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
);
1232 public RegionLCIDMap (int lcid
, int regionId
)
1235 RegionId
= regionId
;
1239 public int RegionId
;