- Try to fix the msvc build.
[mono/afaerber.git] / tools / locale-builder / Driver.cs
blob52586043f241713ffeb53948ee225b67cc290485
1 //
2 // Mono.Tools.LocalBuilder.Driver
3 //
4 // Author(s):
5 // Jackson Harper (jackson@ximian.com)
6 // Atsushi Enomoto (atsushi@ximian.com)
7 //
8 // (C) 2004-2005 Novell, Inc (http://www.novell.com)
9 //
12 using System;
13 using System.IO;
14 using System.Text;
15 using System.Xml;
16 using System.Xml.XPath;
17 using System.Collections;
18 using System.Globalization;
19 using System.Text.RegularExpressions;
21 namespace Mono.Tools.LocaleBuilder {
23 public class Driver {
25 public static void Main (string [] args)
27 Driver d = new Driver ();
28 ParseArgs (args, d);
29 d.Run ();
32 private static void ParseArgs (string [] args, Driver d)
34 for (int i = 0; i < args.Length; i++) {
35 if (args [i] == "--lang" && i+1 < args.Length)
36 d.Lang = args [++i];
37 else if (args [i] == "--locales" && i+1 < args.Length)
38 d.Locales = args [++i];
39 else if (args [i] == "--header" && i + 1 < args.Length)
40 d.HeaderFileName = args [++i];
44 private string lang;
45 private string locales;
46 private string header_name;
47 private ArrayList cultures;
48 private Hashtable langs;
49 private Hashtable currency_types;
50 private Hashtable regions;
52 private XPathDocument lcids_doc;
54 // The lang is the language that display names will be displayed in
55 public string Lang {
56 get {
57 if (lang == null)
58 lang = "en";
59 return lang;
61 set { lang = value; }
64 public string Locales {
65 get { return locales; }
66 set { locales = value; }
69 public string HeaderFileName {
70 get {
71 if (header_name == null)
72 return "culture-info-tables.h";
73 return header_name;
75 set { header_name = value; }
78 public void Run ()
80 lcids_doc = GetXPathDocument ("lcids.xml");
82 Regex locales_regex = null;
83 if (Locales != null)
84 locales_regex = new Regex (Locales);
86 langs = new Hashtable ();
87 cultures = new ArrayList ();
88 regions = new Hashtable ();
90 LookupRegions ();
92 LookupCurrencyTypes ();
94 foreach (string file in Directory.GetFiles ("locales", "*.xml")) {
95 string fn = Path.GetFileNameWithoutExtension (file);
96 if (fn == "hy_AM")
97 continue; // see bug #75499
98 if (locales_regex == null || locales_regex.IsMatch (fn)) {
99 ParseLocale (fn);
103 /* FIXME: This is hacky.
104 * Since there is only langs/zh.xml while there are
105 * two "zh" languages (CHS and CHT), there should be
106 * different language profiles and we are not likely
107 * to add lang/* files. So here I just clone zh-CHS
108 * as zh-CHT
110 foreach (CultureInfoEntry e in cultures) {
111 if (e.Name == "zh-CHS") {
112 CultureInfoEntry t =
113 CultureInfoEntry.ShallowCopy (e);
114 t.Language = "zh-CHT";
115 LookupLcids (t, true);
116 cultures.Add (t);
117 break;
121 ArrayList regionList = new ArrayList (regions.Values);
122 regionList.Sort (RegionComparer.Instance);
123 int number = 0;
124 foreach (RegionInfoEntry r in regionList)
125 r.RegionId = number++;
127 foreach (CultureInfoEntry e in cultures) {
128 int lcid = int.Parse (e.Lcid.Substring (2),
129 NumberStyles.HexNumber);
130 int idx;
131 int start = e.Name.IndexOf ('-') + 1;
132 if (start == 0)
133 continue;
134 for (idx = start; idx < e.Name.Length; idx++)
135 if (!Char.IsLetter (e.Name [idx]))
136 break;
137 if (start == idx) {
138 Console.Error.WriteLine ("Culture {0} {1} is not mappable to Region.", e.Lcid, e.Name);
139 continue;
141 string name = e.Name.Substring (start, idx - start);
142 RegionInfoEntry rm = null;
143 foreach (RegionInfoEntry r in regions.Values)
144 if (r.ISO2Name == name) {
145 rm = r;
146 break;
148 if (rm == null) {
149 Console.Error.WriteLine ("No definition for region {0}", name);
150 continue;
152 e.RegionId = rm.RegionId;
156 * Dump each table individually. Using StringBuilders
157 * because it is easier to debug, should switch to just
158 * writing to streams eventually.
160 using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
161 writer.NewLine = "\n";
162 writer.WriteLine ();
163 writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
164 writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
165 writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
166 writer.WriteLine ("\n");
168 writer.WriteLine ("#define NUM_CULTURE_ENTRIES " + cultures.Count);
169 writer.WriteLine ("#define NUM_REGION_ENTRIES " + regionList.Count);
170 writer.WriteLine ("\n");
172 // Sort the cultures by lcid
173 cultures.Sort (new LcidComparer ());
175 StringBuilder builder = new StringBuilder ();
176 int row = 0;
177 int count = cultures.Count;
178 for (int i = 0; i < count; i++) {
179 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
180 if (ci.DateTimeFormatEntry == null)
181 continue;
182 ci.DateTimeFormatEntry.AppendTableRow (builder);
183 ci.DateTimeFormatEntry.Row = row++;
184 if (i + 1 < count)
185 builder.Append (',');
186 builder.Append ('\n');
189 writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
190 writer.Write (builder);
191 writer.WriteLine ("};\n\n");
193 builder = new StringBuilder ();
194 row = 0;
195 for (int i=0; i < count; i++) {
196 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
197 if (ci.NumberFormatEntry == null)
198 continue;
199 ci.NumberFormatEntry.AppendTableRow (builder);
200 ci.NumberFormatEntry.Row = row++;
201 if (i + 1 < count)
202 builder.Append (',');
203 builder.Append ('\n');
206 writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
207 writer.Write (builder);
208 writer.WriteLine ("};\n\n");
210 builder = new StringBuilder ();
211 row = 0;
212 for (int i = 0; i < count; i++) {
213 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
214 ci.AppendTableRow (builder);
215 ci.Row = row++;
216 if (i + 1 < count)
217 builder.Append (',');
218 builder.Append ('\n');
221 writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
222 writer.Write (builder);
223 writer.WriteLine ("};\n\n");
225 cultures.Sort (new NameComparer ()); // Sort based on name
226 builder = new StringBuilder ();
227 for (int i = 0; i < count; i++) {
228 CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
229 builder.Append ("\t{" + Entry.EncodeStringIdx (ci.Name.ToLower ()) + ", ");
230 builder.Append (ci.Row + "}");
231 if (i + 1 < count)
232 builder.Append (',');
233 builder.Append ('\n');
236 writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
237 writer.Write (builder);
238 writer.WriteLine ("};\n\n");
240 builder = new StringBuilder ();
241 int rcount = 0;
242 foreach (RegionInfoEntry r in regionList) {
243 r.AppendTableRow (builder);
244 if (++rcount != regionList.Count)
245 builder.Append (',');
246 builder.Append ('\n');
248 writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
249 writer.Write (builder);
250 writer.WriteLine ("};\n\n");
252 builder = new StringBuilder ();
253 rcount = 0;
254 foreach (RegionInfoEntry ri in regionList) {
255 builder.Append ("\t{" + Entry.EncodeStringIdx (ri.ISO2Name) + ", ");
256 builder.Append (ri.RegionId + "}");
257 if (++rcount < regionList.Count)
258 builder.Append (',');
259 builder.Append ('\n');
262 writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
263 writer.Write (builder);
264 writer.WriteLine ("};\n\n");
266 writer.WriteLine ("static const char locale_strings [] = {");
267 writer.Write (Entry.GetStrings ());
268 writer.WriteLine ("};\n\n");
270 writer.WriteLine ("#endif\n");
274 private XPathDocument GetXPathDocument (string path)
276 XmlTextReader xtr = null;
277 try {
278 xtr = new XmlTextReader (path);
279 xtr.XmlResolver = null;
280 return new XPathDocument (xtr);
281 } finally {
282 if (xtr != null)
283 xtr.Close ();
287 private string GetShortName (string lang)
289 return lang == "zh-CHS" ? "zh" : lang;
292 private bool ParseLang (string lang)
294 XPathDocument doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
295 XPathNavigator nav = doc.CreateNavigator ();
296 CultureInfoEntry ci = 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))
308 return false;
310 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (Lang) + ".xml"));
311 nav = doc.CreateNavigator ();
312 ci.DisplayName = LookupFullName (ci, nav);
314 if (Lang == "en") {
315 ci.EnglishName = ci.DisplayName;
316 } else {
317 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
318 nav = doc.CreateNavigator ();
319 ci.EnglishName = LookupFullName (ci, nav);
322 if (ci.Language == Lang) {
323 ci.NativeName = ci.DisplayName;
324 } else {
325 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
326 nav = doc.CreateNavigator ();
327 ci.NativeName = LookupFullName (ci, nav);
330 // Null these out because langs dont have them
331 ci.DateTimeFormatEntry = null;
332 ci.NumberFormatEntry = null;
334 langs [lang] = ci;
335 cultures.Add (ci);
337 return true;
340 private void ParseLocale (string locale)
342 CultureInfoEntry ci;
344 ci = LookupCulture (locale);
346 if (ci == null)
347 return;
349 if (langs [GetLanguageFixed (ci)] == null) {
350 if (!ParseLang (GetLanguageFixed (ci))) // If we can't parse the lang we cant have the locale
351 return;
354 cultures.Add (ci);
357 private CultureInfoEntry LookupCulture (string locale)
359 string path = Path.Combine ("locales", locale + ".xml");
360 if (!File.Exists (path))
361 return null;
362 XPathDocument doc = GetXPathDocument (path);
363 XPathNavigator nav = doc.CreateNavigator ();
364 CultureInfoEntry ci = new CultureInfoEntry ();
365 string supp;
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))
374 return null;
375 LookupNames (ci);
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 ();
388 Lookup (nav, ci);
390 doc = GetXPathDocument (Path.Combine ("supp", "root.xml"));
391 nav = doc.CreateNavigator ();
392 Lookup (nav, ci);
394 doc = GetXPathDocument (Path.Combine ("langs", GetShortName (GetLanguageFixed (ci)) + ".xml"));
395 nav = doc.CreateNavigator ();
396 Lookup (nav, ci);
398 supp = Path.Combine ("supp", GetLanguageFixed (ci) + ".xml");
399 if (File.Exists (supp)) {
400 doc = GetXPathDocument (supp);
401 nav = doc.CreateNavigator ();
402 Lookup (nav, ci);
405 doc = GetXPathDocument (Path.Combine ("locales", locale + ".xml"));
406 nav = doc.CreateNavigator ();
407 Lookup (nav, ci);
409 supp = Path.Combine ("supp", locale + ".xml");
410 if (File.Exists (supp)) {
411 doc = GetXPathDocument (supp);
412 nav = doc.CreateNavigator ();
413 Lookup (nav, ci);
416 return ci;
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) {
430 case "nb":
431 case "nn":
432 return "no";
435 return 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);
445 if (Lang == "en") {
446 ci.EnglishName = ci.DisplayName;
447 } else {
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;
455 } else {
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))
469 al.Add (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 ()) {
489 int type;
490 string greg_type_str;
491 XPathNavigator df_nav = ni2.Current;
492 switch (df_nav.GetAttribute ("type", String.Empty)) {
493 case "Gregorian":
494 type = 0;
495 break;
496 case "Hijri":
497 type = 0x01;
498 break;
499 case "ThaiBuddhist":
500 type = 0x02;
501 break;
502 default:
503 Console.WriteLine ("unknown calendar type: " +
504 df_nav.GetAttribute ("type", String.Empty));
505 continue;
507 type <<= 24;
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");
556 string value = null;
557 if (p.MoveNext ())
558 value = p.Current.Value;
559 XPathNodeIterator ext = null;
560 switch (df_nav.GetAttribute ("type", String.Empty)) {
561 case "full":
562 if (value != null)
563 ParseFullDateFormat (df, value);
564 break;
565 case "long":
566 if (value != null)
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);
572 do {
573 df.LongDatePatterns.Add (ext.Current.Value);
574 } while (ext.MoveNext ());
576 else
577 AddPattern (df.LongDatePatterns, df.LongDatePattern);
578 break;
579 case "short":
580 if (value != null)
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);
586 do {
587 df.ShortDatePatterns.Add (ext.Current.Value);
588 } while (ext.MoveNext ());
590 else
591 AddPattern (df.ShortDatePatterns, df.ShortDatePattern);
592 break;
593 case "year_month":
594 if (value != null)
595 df.YearMonthPattern = value;
596 break;
597 case "month_day":
598 if (value != null)
599 df.MonthDayPattern = value;
600 break;
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");
608 string value = null;
609 if (p.MoveNext ())
610 value = p.Current.Value;
611 XPathNodeIterator ext = null;
612 switch (df_nav.GetAttribute ("type", String.Empty)) {
613 case "long":
614 if (value != null)
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);
620 do {
621 df.LongTimePatterns.Add (ext.Current.Value);
622 } while (ext.MoveNext ());
624 else
625 AddPattern (df.LongTimePatterns, df.LongTimePattern);
626 break;
627 case "short":
628 if (value != null)
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);
634 do {
635 df.ShortTimePatterns.Add (ext.Current.Value);
636 } while (ext.MoveNext ());
638 else
639 AddPattern (df.ShortTimePatterns, df.ShortTimePattern);
640 break;
644 ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dateTimeFormats/dateTimeFormatLength/dateTimeFormat/pattern");
645 if (ni2.MoveNext ())
646 df.RawFullDateTimePattern = ni2.Current.ToString ();/*String.Format (ni2.Current.ToString (),
647 df.LongTimePattern, df.LongDatePattern);*/
649 XPathNodeIterator am = ni.Current.SelectChildren ("am", "");
650 if (am.MoveNext ())
651 df.AMDesignator = am.Current.Value;
652 XPathNodeIterator pm = ni.Current.SelectChildren ("pm", "");
653 if (pm.MoveNext ())
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)
694 return;
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 ++;
716 } else
717 break; }
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;
731 } else {
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;
745 } else {
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)
758 return;
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);
776 } else {
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++;
789 else
790 break;
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;
806 } else {
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)
819 return;
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++;
839 else
840 break;
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;
852 } else {
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;
888 } else {
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;
900 } else {
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;
948 if (type == null) {
949 Console.WriteLine ("no currency type for: " + ci.Territory);
950 return;
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);
975 string file;
977 if (ci.Territory != null) {
978 file = Path.Combine ("locales", ci.Language + "_" + ci.Territory + ".xml");
979 Console.WriteLine ("deleting file: " + file);
980 File.Delete (file);
983 return false;
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
995 ci.Lcid = id;
996 ci.ParentLcid = parent;
997 ci.SpecificLcid = specific;
998 ci.ISO2Lang = iso2;
999 ci.ISO3Lang = iso3;
1000 ci.Win3Lang = win;
1001 ci.IcuName = icu;
1003 ci.TextInfoEntry = new TextInfoEntry (int.Parse (id.Substring (2), NumberStyles.HexNumber), GetXPathDocument ("textinfo.xml"));
1005 return true;
1008 private string LookupFullName (CultureInfoEntry ci, XPathNavigator nav)
1010 string pre = "ldml/localeDisplayNames/";
1011 string ret;
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)
1020 return ret;
1021 ret += " (" + (string) nav.Evaluate ("string("+
1022 pre + "territories/territory[@type='" + ci.Territory + "'])") + ")";
1024 return ret;
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", "")];
1047 if (r == null)
1048 continue;
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++) {
1094 char c = full [i];
1095 if (!inquote && c == 'M') {
1096 month_day += c;
1097 year_month += c;
1098 in_year_data = true;
1099 in_month_data = true;
1100 if (month_start == 0)
1101 month_start = i;
1102 month_end = i;
1103 year_end = year_month.Length;
1104 } else if (!inquote && Char.ToLower (c) == 'd') {
1105 month_day += c;
1106 in_month_data = true;
1107 in_year_data = false;
1108 if (day_start == 0)
1109 day_start = i;
1110 day_end = i;
1111 } else if (!inquote && Char.ToLower (c) == 'y') {
1112 year_month += c;
1113 in_year_data = true;
1114 in_month_data = false;
1115 if (year_start == 0)
1116 year_start = i;
1117 year_end = i;
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) {
1122 if (in_month_data)
1123 month_day += c;
1124 if (in_year_data)
1125 year_month += c;
1128 if (c == '\'') {
1129 inquote = !inquote;
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)
1145 int idx = 0;
1146 p = p.Trim ().TrimEnd (',');
1147 idx = p.LastIndexOf ("' de '"); // spanish dates
1148 if (idx > 0)
1149 p = p.Substring (0, idx);
1150 idx = p.LastIndexOf ("' ta '"); // finnish
1151 if (idx > 0)
1152 p = p.Substring (0, idx);
1153 idx = p.LastIndexOf ("'ren'"); // euskara
1154 if (idx > 0)
1155 p = p.Replace ("'ren'", "").Trim ();
1156 idx = p.LastIndexOf ("'a'"); // estonian
1157 if (idx > 0)
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);
1198 class RegionLCIDMap
1200 public RegionLCIDMap (int lcid, int regionId)
1202 LCID = lcid;
1203 RegionId = regionId;
1206 public int LCID;
1207 public int RegionId;