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
);
526 if (df
.MonthNames
.Count
== 12)
527 df
.MonthNames
.Add (String
.Empty
);
529 ni2
= (XPathNodeIterator
) ni
.Current
.Evaluate ("dayNames/day");
530 while (ni2
.MoveNext ()) {
531 if (ni2
.CurrentPosition
== 1)
532 df
.DayNames
.Clear ();
533 df
.DayNames
.Add (ni2
.Current
.Value
);
536 ni2
= (XPathNodeIterator
) ni
.Current
.Evaluate ("dayAbbr/day");
537 while (ni2
.MoveNext ()) {
538 if (ni2
.CurrentPosition
== 1)
539 df
.AbbreviatedDayNames
.Clear ();
540 df
.AbbreviatedDayNames
.Add (ni2
.Current
.Value
);
543 ni2
= (XPathNodeIterator
) ni
.Current
.Evaluate ("monthAbbr/month");
544 while (ni2
.MoveNext ()) {
545 if (ni2
.CurrentPosition
== 1)
546 df
.AbbreviatedMonthNames
.Clear ();
547 df
.AbbreviatedMonthNames
.Add (ni2
.Current
.Value
);
549 if (df
.AbbreviatedMonthNames
.Count
== 12)
550 df
.AbbreviatedMonthNames
.Add (String
.Empty
);
552 ni2
= (XPathNodeIterator
) ni
.Current
.Evaluate ("dateFormats/dateFormatLength");
553 while (ni2
.MoveNext ()) {
554 XPathNavigator df_nav
= ni2
.Current
;
555 XPathNodeIterator p
= df_nav
.Select ("dateFormat/pattern");
558 value = p
.Current
.Value
;
559 XPathNodeIterator ext
= null;
560 switch (df_nav
.GetAttribute ("type", String
.Empty
)) {
563 ParseFullDateFormat (df
, value);
567 df
.LongDatePattern
= value;
568 ext
= df_nav
.Select ("extraPatterns/pattern");
569 if (ext
.MoveNext ()) {
570 df
.LongDatePatterns
.Clear ();
571 AddPattern (df
.LongDatePatterns
, df
.LongDatePattern
);
573 df
.LongDatePatterns
.Add (ext
.Current
.Value
);
574 } while (ext
.MoveNext ());
577 AddPattern (df
.LongDatePatterns
, df
.LongDatePattern
);
581 df
.ShortDatePattern
= value;
582 ext
= df_nav
.Select ("extraPatterns/pattern");
583 if (ext
.MoveNext ()) {
584 df
.ShortDatePatterns
.Clear ();
585 AddPattern (df
.ShortDatePatterns
, df
.ShortDatePattern
);
587 df
.ShortDatePatterns
.Add (ext
.Current
.Value
);
588 } while (ext
.MoveNext ());
591 AddPattern (df
.ShortDatePatterns
, df
.ShortDatePattern
);
595 df
.YearMonthPattern
= value;
599 df
.MonthDayPattern
= value;
604 ni2
= (XPathNodeIterator
) ni
.Current
.Evaluate ("timeFormats/timeFormatLength");
605 while (ni2
.MoveNext ()) {
606 XPathNavigator df_nav
= ni2
.Current
;
607 XPathNodeIterator p
= df_nav
.Select ("timeFormat/pattern");
610 value = p
.Current
.Value
;
611 XPathNodeIterator ext
= null;
612 switch (df_nav
.GetAttribute ("type", String
.Empty
)) {
615 df
.LongTimePattern
= value.Replace ('a', 't');
616 ext
= df_nav
.Select ("extraPatterns/pattern");
617 if (ext
.MoveNext ()) {
618 df
.LongTimePatterns
.Clear ();
619 AddPattern (df
.LongTimePatterns
, df
.LongTimePattern
);
621 df
.LongTimePatterns
.Add (ext
.Current
.Value
);
622 } while (ext
.MoveNext ());
625 AddPattern (df
.LongTimePatterns
, df
.LongTimePattern
);
629 df
.ShortTimePattern
= value.Replace ('a', 't');
630 ext
= df_nav
.Select ("extraPatterns/pattern");
631 if (ext
.MoveNext ()) {
632 df
.ShortTimePatterns
.Clear ();
633 AddPattern (df
.ShortTimePatterns
, df
.ShortTimePattern
);
635 df
.ShortTimePatterns
.Add (ext
.Current
.Value
);
636 } while (ext
.MoveNext ());
639 AddPattern (df
.ShortTimePatterns
, df
.ShortTimePattern
);
644 ni2
= (XPathNodeIterator
) ni
.Current
.Evaluate ("dateTimeFormats/dateTimeFormatLength/dateTimeFormat/pattern");
646 df
.RawFullDateTimePattern
= ni2
.Current
.ToString ();/*String.Format (ni2.Current.ToString (),
647 df.LongTimePattern, df.LongDatePattern);*/
649 XPathNodeIterator am
= ni
.Current
.SelectChildren ("am", "");
651 df
.AMDesignator
= am
.Current
.Value
;
652 XPathNodeIterator pm
= ni
.Current
.SelectChildren ("pm", "");
654 df
.PMDesignator
= pm
.Current
.Value
;
656 string am = (string) ni.Current.Evaluate ("string(am)");
657 string pm = (string) ni.Current.Evaluate ("string(pm)");
659 if (am != String.Empty)
660 df.AMDesignator = am;
661 if (pm != String.Empty)
662 df.PMDesignator = pm;
666 string date_sep
= (string) nav
.Evaluate ("string(ldml/dates/symbols/dateSeparator)");
667 string time_sep
= (string) nav
.Evaluate ("string(ldml/dates/symbols/timeSeparator)");
669 if (date_sep
!= String
.Empty
)
670 ci
.DateTimeFormatEntry
.DateSeparator
= date_sep
;
671 if (time_sep
!= String
.Empty
)
672 ci
.DateTimeFormatEntry
.TimeSeparator
= time_sep
;
675 private void LookupNumberInfo (XPathNavigator nav
, CultureInfoEntry ci
)
677 XPathNodeIterator ni
=(XPathNodeIterator
) nav
.Evaluate ("ldml/numbers");
679 while (ni
.MoveNext ()) {
680 LookupNumberSymbols (ni
.Current
, ci
);
681 LookupDecimalFormat (ni
.Current
, ci
);
682 LookupPercentFormat (ni
.Current
, ci
);
683 LookupCurrencyFormat (ni
.Current
, ci
);
684 LookupCurrencySymbol (ni
.Current
, ci
);
688 private void LookupDecimalFormat (XPathNavigator nav
, CultureInfoEntry ci
)
690 string format
= (string) nav
.Evaluate ("string(decimalFormats/" +
691 "decimalFormatLength/decimalFormat/pattern)");
693 if (format
== String
.Empty
)
696 string [] part_one
, part_two
;
697 string [] pos_neg
= format
.Split (new char [1] {';'}
, 2);
699 // Most of the patterns are common in positive and negative
700 if (pos_neg
.Length
== 1)
701 pos_neg
= new string [] {pos_neg [0], pos_neg [0]}
;
703 if (pos_neg
.Length
== 2) {
705 part_one
= pos_neg
[0].Split (new char [1] {'.'}
, 2);
706 if (part_one
.Length
== 1)
707 part_one
= new string [] {part_one [0], String.Empty}
;
709 if (part_one
.Length
== 2) {
710 // assumed same for both positive and negative
711 // decimal digit side
712 ci
.NumberFormatEntry
.NumberDecimalDigits
= 0;
713 for (int i
= 0; i
< part_one
[1].Length
; i
++) {
714 if (part_one
[1][i
] == '#') {
715 ci
.NumberFormatEntry
.NumberDecimalDigits
++;
718 // FIXME: This should be actually done by modifying culture xml files, but too many files to be modified.
719 if (ci
.NumberFormatEntry
.NumberDecimalDigits
> 0)
720 ci
.NumberFormatEntry
.NumberDecimalDigits
--;
722 // decimal grouping side
723 part_two
= part_one
[0].Split (',');
724 if (part_two
.Length
> 1) {
725 int len
= part_two
.Length
- 1;
726 ci
.NumberFormatEntry
.NumberGroupSizes
= new int [len
];
727 for (int i
= 0; i
< len
; i
++) {
728 string pat
= part_two
[i
+ 1];
729 ci
.NumberFormatEntry
.NumberGroupSizes
[i
] = pat
.Length
;
732 ci
.NumberFormatEntry
.NumberGroupSizes
= new int [1] { 3 }
;
735 if (pos_neg
[1].StartsWith ("(") && pos_neg
[1].EndsWith (")")) {
736 ci
.NumberFormatEntry
.NumberNegativePattern
= 0;
737 } else if (pos_neg
[1].StartsWith ("- ")) {
738 ci
.NumberFormatEntry
.NumberNegativePattern
= 2;
739 } else if (pos_neg
[1].StartsWith ("-")) {
740 ci
.NumberFormatEntry
.NumberNegativePattern
= 1;
741 } else if (pos_neg
[1].EndsWith (" -")) {
742 ci
.NumberFormatEntry
.NumberNegativePattern
= 4;
743 } else if (pos_neg
[1].EndsWith ("-")) {
744 ci
.NumberFormatEntry
.NumberNegativePattern
= 3;
746 ci
.NumberFormatEntry
.NumberNegativePattern
= 1;
752 private void LookupPercentFormat (XPathNavigator nav
, CultureInfoEntry ci
)
754 string format
= (string) nav
.Evaluate ("string(percentFormats/" +
755 "percentFormatLength/percentFormat/pattern)");
757 if (format
== String
.Empty
)
760 string [] part_one
, part_two
;
762 // we don't have percentNegativePattern in CLDR so
763 // the percentNegativePattern are just guesses
764 if (format
.StartsWith ("%")) {
765 ci
.NumberFormatEntry
.PercentPositivePattern
= 2;
766 ci
.NumberFormatEntry
.PercentNegativePattern
= 2;
767 format
= format
.Substring (1);
768 } else if (format
.EndsWith (" %")) {
769 ci
.NumberFormatEntry
.PercentPositivePattern
= 0;
770 ci
.NumberFormatEntry
.PercentNegativePattern
= 0;
771 format
= format
.Substring (0, format
.Length
- 2);
772 } else if (format
.EndsWith ("%")) {
773 ci
.NumberFormatEntry
.PercentPositivePattern
= 1;
774 ci
.NumberFormatEntry
.PercentNegativePattern
= 1;
775 format
= format
.Substring (0, format
.Length
- 1);
777 ci
.NumberFormatEntry
.PercentPositivePattern
= 0;
778 ci
.NumberFormatEntry
.PercentNegativePattern
= 0;
781 part_one
= format
.Split (new char [1] {'.'}
, 2);
782 if (part_one
.Length
== 2) {
783 // assumed same for both positive and negative
784 // decimal digit side
785 ci
.NumberFormatEntry
.PercentDecimalDigits
= 0;
786 for (int i
= 0; i
< part_one
[1].Length
; i
++) {
787 if (part_one
[1][i
] == '#')
788 ci
.NumberFormatEntry
.PercentDecimalDigits
++;
794 if (part_one
.Length
> 0) {
795 // percent grouping side
796 part_two
= part_one
[0].Split (',');
797 if (part_two
.Length
> 1) {
798 int len
= part_two
.Length
- 1;
799 ci
.NumberFormatEntry
.PercentGroupSizes
= new int [len
];
800 for (int i
= 0; i
< len
; i
++) {
801 string pat
= part_two
[i
+ 1];
802 if (pat
[pat
.Length
-1] == '0')
803 ci
.NumberFormatEntry
.PercentDecimalDigits
= pat
.Length
- 1;
804 ci
.NumberFormatEntry
.PercentGroupSizes
[i
] = pat
.Length
;
807 ci
.NumberFormatEntry
.PercentGroupSizes
= new int [1] { 3 }
;
808 ci
.NumberFormatEntry
.PercentDecimalDigits
= 2;
813 private void LookupCurrencyFormat (XPathNavigator nav
, CultureInfoEntry ci
)
815 string format
= (string) nav
.Evaluate ("string(currencyFormats/" +
816 "currencyFormatLength/currencyFormat/pattern)");
818 if (format
== String
.Empty
)
821 string [] part_one
, part_two
;
822 string [] pos_neg
= format
.Split (new char [1] {';'}
, 2);
824 // Most of the patterns are common in positive and negative
825 if (pos_neg
.Length
== 1)
826 pos_neg
= new string [] {pos_neg [0], pos_neg [0]}
;
828 if (pos_neg
.Length
== 2) {
829 part_one
= pos_neg
[0].Split (new char [1] {'.'}
, 2);
830 if (part_one
.Length
== 1)
831 part_one
= new string [] {part_one [0], String.Empty}
;
832 if (part_one
.Length
== 2) {
833 // assumed same for both positive and negative
834 // decimal digit side
835 ci
.NumberFormatEntry
.CurrencyDecimalDigits
= 0;
836 for (int i
= 0; i
< part_one
[1].Length
; i
++) {
837 if (part_one
[1][i
] == '0')
838 ci
.NumberFormatEntry
.CurrencyDecimalDigits
++;
843 // decimal grouping side
844 part_two
= part_one
[0].Split (',');
845 if (part_two
.Length
> 1) {
846 int len
= part_two
.Length
- 1;
847 ci
.NumberFormatEntry
.CurrencyGroupSizes
= new int [len
];
848 for (int i
= 0; i
< len
; i
++) {
849 string pat
= part_two
[i
+ 1];
850 ci
.NumberFormatEntry
.CurrencyGroupSizes
[i
] = pat
.Length
;
853 ci
.NumberFormatEntry
.CurrencyGroupSizes
= new int [1] { 3 }
;
856 if (pos_neg
[1].StartsWith ("(\u00a4 ") && pos_neg
[1].EndsWith (")")) {
857 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 14;
858 } else if (pos_neg
[1].StartsWith ("(\u00a4") && pos_neg
[1].EndsWith (")")) {
859 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 0;
860 } else if (pos_neg
[1].StartsWith ("\u00a4 ") && pos_neg
[1].EndsWith ("-")) {
861 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 11;
862 } else if (pos_neg
[1].StartsWith ("\u00a4") && pos_neg
[1].EndsWith ("-")) {
863 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 3;
864 } else if (pos_neg
[1].StartsWith ("(") && pos_neg
[1].EndsWith (" \u00a4")) {
865 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 15;
866 } else if (pos_neg
[1].StartsWith ("(") && pos_neg
[1].EndsWith ("\u00a4")) {
867 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 4;
868 } else if (pos_neg
[1].StartsWith ("-") && pos_neg
[1].EndsWith (" \u00a4")) {
869 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 8;
870 } else if (pos_neg
[1].StartsWith ("-") && pos_neg
[1].EndsWith ("\u00a4")) {
871 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 5;
872 } else if (pos_neg
[1].StartsWith ("-\u00a4 ")) {
873 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 9;
874 } else if (pos_neg
[1].StartsWith ("-\u00a4")) {
875 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 1;
876 } else if (pos_neg
[1].StartsWith ("\u00a4 -")) {
877 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 12;
878 } else if (pos_neg
[1].StartsWith ("\u00a4-")) {
879 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 2;
880 } else if (pos_neg
[1].EndsWith (" \u00a4-")) {
881 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 10;
882 } else if (pos_neg
[1].EndsWith ("\u00a4-")) {
883 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 7;
884 } else if (pos_neg
[1].EndsWith ("- \u00a4")) {
885 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 13;
886 } else if (pos_neg
[1].EndsWith ("-\u00a4")) {
887 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 6;
889 ci
.NumberFormatEntry
.CurrencyNegativePattern
= 0;
892 if (pos_neg
[0].StartsWith ("\u00a4 ")) {
893 ci
.NumberFormatEntry
.CurrencyPositivePattern
= 2;
894 } else if (pos_neg
[0].StartsWith ("\u00a4")) {
895 ci
.NumberFormatEntry
.CurrencyPositivePattern
= 0;
896 } else if (pos_neg
[0].EndsWith (" \u00a4")) {
897 ci
.NumberFormatEntry
.CurrencyPositivePattern
= 3;
898 } else if (pos_neg
[0].EndsWith ("\u00a4")) {
899 ci
.NumberFormatEntry
.CurrencyPositivePattern
= 1;
901 ci
.NumberFormatEntry
.CurrencyPositivePattern
= 0;
907 private void LookupNumberSymbols (XPathNavigator nav
, CultureInfoEntry ci
)
909 string dec
= (string) nav
.Evaluate ("string(symbols/decimal)");
910 string group = (string) nav
.Evaluate ("string(symbols/group)");
911 string percent
= (string) nav
.Evaluate ("string(symbols/percentSign)");
912 string positive
= (string) nav
.Evaluate ("string(symbols/plusSign)");
913 string negative
= (string) nav
.Evaluate ("string(symbols/minusSign)");
914 string per_mille
= (string) nav
.Evaluate ("string(symbols/perMille)");
915 string infinity
= (string) nav
.Evaluate ("string(symbols/infinity)");
916 string nan
= (string) nav
.Evaluate ("string(symbols/nan)");
918 if (dec
!= String
.Empty
) {
919 ci
.NumberFormatEntry
.NumberDecimalSeparator
= dec
;
920 ci
.NumberFormatEntry
.PercentDecimalSeparator
= dec
;
921 ci
.NumberFormatEntry
.CurrencyDecimalSeparator
= dec
;
924 if (group != String
.Empty
) {
925 ci
.NumberFormatEntry
.NumberGroupSeparator
= group;
926 ci
.NumberFormatEntry
.PercentGroupSeparator
= group;
927 ci
.NumberFormatEntry
.CurrencyGroupSeparator
= group;
930 if (percent
!= String
.Empty
)
931 ci
.NumberFormatEntry
.PercentSymbol
= percent
;
932 if (positive
!= String
.Empty
)
933 ci
.NumberFormatEntry
.PositiveSign
= positive
;
934 if (negative
!= String
.Empty
)
935 ci
.NumberFormatEntry
.NegativeSign
= negative
;
936 if (per_mille
!= String
.Empty
)
937 ci
.NumberFormatEntry
.PerMilleSymbol
= per_mille
;
938 if (infinity
!= String
.Empty
)
939 ci
.NumberFormatEntry
.PositiveInfinitySymbol
= infinity
;
940 if (nan
!= String
.Empty
)
941 ci
.NumberFormatEntry
.NaNSymbol
= nan
;
944 private void LookupCurrencySymbol (XPathNavigator nav
, CultureInfoEntry ci
)
946 string type
= currency_types
[ci
.Territory
] as string;
949 Console
.WriteLine ("no currency type for: " + ci
.Territory
);
953 string cur
= (string) nav
.Evaluate ("string(currencies/currency [@type='" +
954 type
+ "']/symbol)");
956 if (cur
!= String
.Empty
)
957 ci
.NumberFormatEntry
.CurrencySymbol
= cur
;
960 private bool LookupLcids (CultureInfoEntry ci
, bool lang
)
962 XPathNavigator nav
= lcids_doc
.CreateNavigator ();
963 string name
= ci
.Name
;
964 // Language name does not always consist of locale name.
965 // (for zh-* it must be either zh-CHS or zh-CHT)
966 string langName
= GetLanguageFixed (ci
);
968 // if (ci.Territory != null)
969 // name += "-" + ci.Territory;
971 XPathNodeIterator ni
=(XPathNodeIterator
) nav
.Evaluate ("lcids/lcid[@name='"
972 + (lang
? langName
: name
) + "']");
973 if (!ni
.MoveNext ()) {
974 Console
.WriteLine ("no lcid found for: {0} ({1}/{2})", name
, ci
.Language
, ci
.Territory
);
977 if (ci
.Territory
!= null) {
978 file
= Path
.Combine ("locales", ci
.Language
+ "_" + ci
.Territory
+ ".xml");
979 Console
.WriteLine ("deleting file: " + file
);
986 string id
= ni
.Current
.GetAttribute ("id", String
.Empty
);
987 string parent
= ni
.Current
.GetAttribute ("parent", String
.Empty
);
988 string specific
= ni
.Current
.GetAttribute ("specific", String
.Empty
);
989 string iso2
= ni
.Current
.GetAttribute ("iso2", String
.Empty
);
990 string iso3
= ni
.Current
.GetAttribute ("iso3", String
.Empty
);
991 string win
= ni
.Current
.GetAttribute ("win", String
.Empty
);
992 string icu
= ni
.Current
.GetAttribute ("icu_name", String
.Empty
);
994 // lcids are in 0x<hex> format
996 ci
.ParentLcid
= parent
;
997 ci
.SpecificLcid
= specific
;
1003 ci
.TextInfoEntry
= new TextInfoEntry (int.Parse (id
.Substring (2), NumberStyles
.HexNumber
), GetXPathDocument ("textinfo.xml"));
1008 private string LookupFullName (CultureInfoEntry ci
, XPathNavigator nav
)
1010 string pre
= "ldml/localeDisplayNames/";
1013 // FIXME: We use ci.Language here.
1014 // This is nothing more than hack for nb-NO or nn-NO
1015 // where Parent of them is nn (not nb or nn).
1016 ret
= (string) nav
.Evaluate ("string("+
1017 pre
+ "languages/language[@type='" + GetShortName (ci
.Language
) + "'])");
1019 if (ci
.Territory
== null)
1021 ret
+= " (" + (string) nav
.Evaluate ("string("+
1022 pre
+ "territories/territory[@type='" + ci
.Territory
+ "'])") + ")";
1027 private void LookupRegions ()
1029 XPathDocument doc
= GetXPathDocument ("supplementalData.xml");
1030 XPathNavigator nav
= doc
.CreateNavigator ();
1031 XPathNodeIterator ni
= nav
.Select ("supplementalData/currencyData/region");
1032 while (ni
.MoveNext ()) {
1033 string territory
= (string) ni
.Current
.GetAttribute ("iso3166", String
.Empty
);
1034 string currency
= (string) ni
.Current
.Evaluate ("string(currency/@iso4217)");
1035 RegionInfoEntry region
= new RegionInfoEntry ();
1036 region
.ISO2Name
= territory
.ToUpper ();
1037 region
.ISOCurrencySymbol
= currency
;
1038 regions
[territory
] = region
;
1041 doc
= GetXPathDocument ("langs/en.xml");
1042 nav
= doc
.CreateNavigator ();
1043 ni
= nav
.Select ("/ldml/localeDisplayNames/territories/territory");
1044 while (ni
.MoveNext ()) {
1045 RegionInfoEntry r
= (RegionInfoEntry
)
1046 regions
[ni
.Current
.GetAttribute ("type", "")];
1049 r
.EnglishName
= ni
.Current
.Value
;
1052 Hashtable curNames
= new Hashtable ();
1053 ni
= nav
.Select ("/ldml/numbers/currencies/currency");
1054 while (ni
.MoveNext ())
1055 curNames
[ni
.Current
.GetAttribute ("type", "")] =
1056 ni
.Current
.Evaluate ("string (displayName)");
1058 foreach (RegionInfoEntry r
in regions
.Values
)
1059 r
.CurrencyEnglishName
=
1060 (string) curNames
[r
.ISOCurrencySymbol
];
1063 private void LookupCurrencyTypes ()
1065 XPathDocument doc
= GetXPathDocument ("supplementalData.xml");
1066 XPathNavigator nav
= doc
.CreateNavigator ();
1068 currency_types
= new Hashtable ();
1070 XPathNodeIterator ni
=(XPathNodeIterator
) nav
.Evaluate ("supplementalData/currencyData/region");
1071 while (ni
.MoveNext ()) {
1072 string territory
= (string) ni
.Current
.GetAttribute ("iso3166", String
.Empty
);
1073 string currency
= (string) ni
.Current
.Evaluate ("string(currency/@iso4217)");
1074 currency_types
[territory
] = currency
;
1078 static string control_chars
= "eghmsftz";
1080 // HACK: We are trying to build year_month and month_day patterns from the full pattern.
1081 private void ParseFullDateFormat (DateTimeFormatEntry df
, string full
)
1084 string month_day
= String
.Empty
;
1085 string year_month
= String
.Empty
;
1086 bool in_month_data
= false;
1087 bool in_year_data
= false;
1088 int day_start
= 0, day_end
= 0;
1089 int month_start
= 0, month_end
= 0;
1090 int year_start
= 0, year_end
= 0;
1091 bool inquote
= false;
1093 for (int i
= 0; i
< full
.Length
; i
++) {
1095 if (!inquote
&& c
== 'M') {
1098 in_year_data
= true;
1099 in_month_data
= true;
1100 if (month_start
== 0)
1103 year_end
= year_month
.Length
;
1104 } else if (!inquote
&& Char
.ToLower (c
) == 'd') {
1106 in_month_data
= true;
1107 in_year_data
= false;
1111 } else if (!inquote
&& Char
.ToLower (c
) == 'y') {
1113 in_year_data
= true;
1114 in_month_data
= false;
1115 if (year_start
== 0)
1118 } else if (!inquote
&& control_chars
.IndexOf (Char
.ToLower (c
)) >= 0) {
1119 in_year_data
= false;
1120 in_month_data
= false;
1121 } else if (in_year_data
|| in_month_data
) {
1133 if (month_day
!= String
.Empty
) {
1134 //month_day = month_day.Substring (0, month_end);
1135 df
.MonthDayPattern
= TrimPattern (month_day
);
1137 if (year_month
!= String
.Empty
) {
1138 //year_month = year_month.Substring (0, year_end);
1139 df
.YearMonthPattern
= TrimPattern (year_month
);
1143 string TrimPattern (string p
)
1146 p
= p
.Trim ().TrimEnd (',');
1147 idx
= p
.LastIndexOf ("' de '"); // spanish dates
1149 p
= p
.Substring (0, idx
);
1150 idx
= p
.LastIndexOf ("' ta '"); // finnish
1152 p
= p
.Substring (0, idx
);
1153 idx
= p
.LastIndexOf ("'ren'"); // euskara
1155 p
= p
.Replace ("'ren'", "").Trim ();
1156 idx
= p
.LastIndexOf ("'a'"); // estonian
1158 p
= p
.Substring (0, idx
);
1160 return p
.Replace ("'ta '", "'ta'"); // finnish
1163 private class LcidComparer
: IComparer
{
1165 public int Compare (object a
, object b
)
1167 CultureInfoEntry aa
= (CultureInfoEntry
) a
;
1168 CultureInfoEntry bb
= (CultureInfoEntry
) b
;
1170 return aa
.Lcid
.CompareTo (bb
.Lcid
);
1174 private class NameComparer
: IComparer
{
1176 public int Compare (object a
, object b
)
1178 CultureInfoEntry aa
= (CultureInfoEntry
) a
;
1179 CultureInfoEntry bb
= (CultureInfoEntry
) b
;
1181 return String
.CompareOrdinal(aa
.Name
.ToLower (), bb
.Name
.ToLower ());
1185 class RegionComparer
: IComparer
1187 public static RegionComparer Instance
= new RegionComparer ();
1189 public int Compare (object o1
, object o2
)
1191 RegionInfoEntry r1
= (RegionInfoEntry
) o1
;
1192 RegionInfoEntry r2
= (RegionInfoEntry
) o2
;
1193 return String
.CompareOrdinal (
1194 r1
.ISO2Name
, r2
.ISO2Name
);
1200 public RegionLCIDMap (int lcid
, int regionId
)
1203 RegionId
= regionId
;
1207 public int RegionId
;