+//
+// Driver.cs
//
-// Mono.Tools.LocalBuilder.Driver
-//
-// Author(s):
+// Authors:
// Jackson Harper (jackson@ximian.com)
// Atsushi Enomoto (atsushi@ximian.com)
+// Marek Safar <marek.safar@gmail.com>
//
// (C) 2004-2005 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-
using System;
using System.IO;
using System.Text;
using System.Xml;
-using System.Xml.XPath;
-using System.Collections;
using System.Globalization;
using System.Text.RegularExpressions;
+using System.Collections.Generic;
+using System.Linq;
-namespace Mono.Tools.LocaleBuilder {
+namespace Mono.Tools.LocaleBuilder
+{
+ public class Driver
+ {
+ static readonly string data_root = Path.Combine ("CLDR", "common");
- public class Driver {
-
- public static void Main (string [] args)
+ public static void Main (string[] args)
{
Driver d = new Driver ();
ParseArgs (args, d);
d.Run ();
}
- private static void ParseArgs (string [] args, Driver d)
+ private static void ParseArgs (string[] args, Driver d)
{
for (int i = 0; i < args.Length; i++) {
- if (args [i] == "--lang" && i+1 < args.Length)
- d.Lang = args [++i];
- else if (args [i] == "--locales" && i+1 < args.Length)
- d.Locales = args [++i];
- else if (args [i] == "--header" && i + 1 < args.Length)
- d.HeaderFileName = args [++i];
+ if (args[i] == "--lang" && i + 1 < args.Length)
+ d.Lang = args[++i];
+ else if (args[i] == "--locales" && i + 1 < args.Length)
+ d.Locales = args[++i];
+ else if (args[i] == "--header" && i + 1 < args.Length)
+ d.HeaderFileName = args[++i];
+ else if (args[i] == "--compare")
+ d.OutputCompare = true;
}
}
private string lang;
private string locales;
- private string header_name;
- private ArrayList cultures;
- private Hashtable langs;
- private Hashtable currency_types;
- private Hashtable regions;
-
- private XPathDocument lcids_doc;
+ private string header_name;
+ List<CultureInfoEntry> cultures;
+ Dictionary<string, string> region_currency;
+ Dictionary<string, string> currency_fractions;
// The lang is the language that display names will be displayed in
- public string Lang {
- get {
+ public string Lang
+ {
+ get
+ {
if (lang == null)
lang = "en";
return lang;
set { lang = value; }
}
- public string Locales {
+ public string Locales
+ {
get { return locales; }
set { locales = value; }
}
- public string HeaderFileName {
- get {
- if (header_name == null)
- return "culture-info-tables.h";
- return header_name;
- }
- set { header_name = value; }
- }
-
- public void Run ()
+ public string HeaderFileName
{
- lcids_doc = GetXPathDocument ("lcids.xml");
-
- Regex locales_regex = null;
- if (Locales != null)
- locales_regex = new Regex (Locales);
-
- langs = new Hashtable ();
- cultures = new ArrayList ();
- regions = new Hashtable ();
-
- LookupRegions ();
-
- LookupCurrencyTypes ();
-
- foreach (string file in Directory.GetFiles ("locales", "*.xml")) {
- string fn = Path.GetFileNameWithoutExtension (file);
- if (fn == "hy_AM")
- continue; // see bug #75499
- if (locales_regex == null || locales_regex.IsMatch (fn)) {
- ParseLocale (fn);
- }
- }
-
- /* FIXME: This is hacky.
- * Since there is only langs/zh.xml while there are
- * two "zh" languages (CHS and CHT), there should be
- * different language profiles and we are not likely
- * to add lang/* files. So here I just clone zh-CHS
- * as zh-CHT
- */
- foreach (CultureInfoEntry e in cultures) {
- if (e.Name == "zh-CHS") {
- CultureInfoEntry t =
- CultureInfoEntry.ShallowCopy (e);
- t.Language = "zh-CHT";
- LookupLcids (t, true);
- cultures.Add (t);
- break;
- }
- }
-
- ArrayList regionList = new ArrayList (regions.Values);
- regionList.Sort (RegionComparer.Instance);
- int number = 0;
- foreach (RegionInfoEntry r in regionList)
- r.RegionId = number++;
-
- foreach (CultureInfoEntry e in cultures) {
- int lcid = int.Parse (e.Lcid.Substring (2),
- NumberStyles.HexNumber);
- int idx;
- int start = e.Name.IndexOf ('-') + 1;
- if (start == 0)
- continue;
- for (idx = start; idx < e.Name.Length; idx++)
- if (!Char.IsLetter (e.Name [idx]))
- break;
- if (start == idx) {
- Console.Error.WriteLine ("Culture {0} {1} is not mappable to Region.", e.Lcid, e.Name);
- continue;
- }
- string name = e.Name.Substring (start, idx - start);
- RegionInfoEntry rm = null;
- foreach (RegionInfoEntry r in regions.Values)
- if (r.ISO2Name == name) {
- rm = r;
- break;
- }
- if (rm == null) {
- Console.Error.WriteLine ("No definition for region {0}", name);
- continue;
- }
- e.RegionId = rm.RegionId;
+ get
+ {
+ if (header_name == null)
+ return "culture-info-tables.h";
+ return header_name;
}
+ set { header_name = value; }
+ }
- /**
- * Dump each table individually. Using StringBuilders
- * because it is easier to debug, should switch to just
- * writing to streams eventually.
- */
- using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
- writer.NewLine = "\n";
- writer.WriteLine ();
- writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
- writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
- writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
- writer.WriteLine ("\n");
-
- writer.WriteLine ("#define NUM_CULTURE_ENTRIES " + cultures.Count);
- writer.WriteLine ("#define NUM_REGION_ENTRIES " + regionList.Count);
- writer.WriteLine ("\n");
-
- // Sort the cultures by lcid
- cultures.Sort (new LcidComparer ());
-
- StringBuilder builder = new StringBuilder ();
- int row = 0;
- int count = cultures.Count;
- for (int i = 0; i < count; i++) {
- CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
- if (ci.DateTimeFormatEntry == null)
- continue;
- ci.DateTimeFormatEntry.AppendTableRow (builder);
- ci.DateTimeFormatEntry.Row = row++;
- if (i + 1 < count)
- builder.Append (',');
- builder.Append ('\n');
- }
-
- writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
- writer.Write (builder);
- writer.WriteLine ("};\n\n");
-
- builder = new StringBuilder ();
- row = 0;
- for (int i=0; i < count; i++) {
- CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
- if (ci.NumberFormatEntry == null)
- continue;
- ci.NumberFormatEntry.AppendTableRow (builder);
- ci.NumberFormatEntry.Row = row++;
- if (i + 1 < count)
- builder.Append (',');
- builder.Append ('\n');
- }
-
- writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
- writer.Write (builder);
- writer.WriteLine ("};\n\n");
-
- builder = new StringBuilder ();
- row = 0;
- for (int i = 0; i < count; i++) {
- CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
- ci.AppendTableRow (builder);
- ci.Row = row++;
- if (i + 1 < count)
- builder.Append (',');
- builder.Append ('\n');
- }
-
- writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
- writer.Write (builder);
- writer.WriteLine ("};\n\n");
-
- cultures.Sort (new NameComparer ()); // Sort based on name
- builder = new StringBuilder ();
- for (int i = 0; i < count; i++) {
- CultureInfoEntry ci = (CultureInfoEntry) cultures [i];
- builder.Append ("\t{" + Entry.EncodeStringIdx (ci.Name.ToLower ()) + ", ");
- builder.Append (ci.Row + "}");
- if (i + 1 < count)
- builder.Append (',');
- builder.Append ('\n');
- }
-
- writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
- writer.Write (builder);
- writer.WriteLine ("};\n\n");
+ public bool OutputCompare { get; set; }
- builder = new StringBuilder ();
- int rcount = 0;
- foreach (RegionInfoEntry r in regionList) {
- r.AppendTableRow (builder);
- if (++rcount != regionList.Count)
- builder.Append (',');
- builder.Append ('\n');
+ void Print ()
+ {
+ cultures.Sort ((a, b) => int.Parse (a.LCID.Substring (2), NumberStyles.HexNumber).CompareTo (int.Parse (b.LCID.Substring (2), NumberStyles.HexNumber)));
+
+ var writer = Console.Out;
+
+ foreach (var c in cultures) {
+ writer.WriteLine ("Name: {0}, LCID {1}", c.OriginalName, c.LCID);
+
+ writer.WriteLine ("{0}: {1}", "DisplayName", c.DisplayName);
+ writer.WriteLine ("{0}: {1}", "EnglishName", c.EnglishName);
+ writer.WriteLine ("{0}: {1}", "NativeName", c.NativeName);
+ // writer.WriteLine ("{0}: {1}", "OptionalCalendars", c.OptionalCalendars);
+ writer.WriteLine ("{0}: {1}", "ThreeLetterISOLanguageName", c.ThreeLetterISOLanguageName);
+ writer.WriteLine ("{0}: {1}", "ThreeLetterWindowsLanguageName", c.ThreeLetterWindowsLanguageName);
+ writer.WriteLine ("{0}: {1}", "TwoLetterISOLanguageName", c.TwoLetterISOLanguageName);
+ writer.WriteLine ("{0}: {1}", "Calendar", GetCalendarType (c.CalendarType));
+
+ var df = c.DateTimeFormatEntry;
+ writer.WriteLine ("-- DateTimeFormat --");
+ Dump (writer, df.AbbreviatedDayNames, "AbbreviatedDayNames");
+ Dump (writer, df.AbbreviatedMonthGenitiveNames, "AbbreviatedMonthGenitiveNames");
+ Dump (writer, df.AbbreviatedMonthNames, "AbbreviatedMonthNames");
+ writer.WriteLine ("{0}: {1}", "AMDesignator", df.AMDesignator);
+ writer.WriteLine ("{0}: {1}", "CalendarWeekRule", (CalendarWeekRule) df.CalendarWeekRule);
+ writer.WriteLine ("{0}: {1}", "DateSeparator", df.DateSeparator);
+ Dump (writer, df.DayNames, "DayNames");
+ writer.WriteLine ("{0}: {1}", "FirstDayOfWeek", (DayOfWeek) df.FirstDayOfWeek);
+// Dump (writer, df.GetAllDateTimePatterns (), "GetAllDateTimePatterns");
+ writer.WriteLine ("{0}: {1}", "LongDatePattern", df.LongDatePattern);
+ writer.WriteLine ("{0}: {1}", "LongTimePattern", df.LongTimePattern);
+ writer.WriteLine ("{0}: {1}", "MonthDayPattern", df.MonthDayPattern);
+ Dump (writer, df.MonthGenitiveNames, "MonthGenitiveNames");
+ Dump (writer, df.MonthNames, "MonthNames");
+ writer.WriteLine ("{0}: {1}", "NativeCalendarName", df.NativeCalendarName);
+ writer.WriteLine ("{0}: {1}", "PMDesignator", df.PMDesignator);
+ writer.WriteLine ("{0}: {1}", "ShortDatePattern", df.ShortDatePattern);
+ Dump (writer, df.ShortestDayNames, "ShortestDayNames");
+ writer.WriteLine ("{0}: {1}", "ShortTimePattern", df.ShortTimePattern);
+ writer.WriteLine ("{0}: {1}", "TimeSeparator", df.TimeSeparator);
+ writer.WriteLine ("{0}: {1}", "YearMonthPattern", df.YearMonthPattern);
+
+ var ti = c.TextInfoEntry;
+ writer.WriteLine ("-- TextInfo --");
+ writer.WriteLine ("{0}: {1}", "ANSICodePage", ti.ANSICodePage);
+ writer.WriteLine ("{0}: {1}", "EBCDICCodePage", ti.EBCDICCodePage);
+ writer.WriteLine ("{0}: {1}", "IsRightToLeft", ti.IsRightToLeft);
+ writer.WriteLine ("{0}: {1}", "ListSeparator", ti.ListSeparator);
+ writer.WriteLine ("{0}: {1}", "MacCodePage", ti.MacCodePage);
+ writer.WriteLine ("{0}: {1}", "OEMCodePage", ti.OEMCodePage);
+
+ var nf = c.NumberFormatEntry;
+ writer.WriteLine ("-- NumberFormat --");
+ writer.WriteLine ("{0}: {1}", "CurrencyDecimalDigits", nf.CurrencyDecimalDigits);
+ writer.WriteLine ("{0}: {1}", "CurrencyDecimalSeparator", nf.CurrencyDecimalSeparator);
+ writer.WriteLine ("{0}: {1}", "CurrencyGroupSeparator", nf.CurrencyGroupSeparator);
+ Dump (writer, nf.CurrencyGroupSizes, "CurrencyGroupSizes", true);
+ writer.WriteLine ("{0}: {1}", "CurrencyNegativePattern", nf.CurrencyNegativePattern);
+ writer.WriteLine ("{0}: {1}", "CurrencyPositivePattern", nf.CurrencyPositivePattern);
+ writer.WriteLine ("{0}: {1}", "CurrencySymbol", nf.CurrencySymbol);
+ writer.WriteLine ("{0}: {1}", "DigitSubstitution", nf.DigitSubstitution);
+ writer.WriteLine ("{0}: {1}", "NaNSymbol", nf.NaNSymbol);
+ Dump (writer, nf.NativeDigits, "NativeDigits");
+ writer.WriteLine ("{0}: {1}", "NegativeInfinitySymbol", nf.NegativeInfinitySymbol);
+ writer.WriteLine ("{0}: {1}", "NegativeSign", nf.NegativeSign);
+ writer.WriteLine ("{0}: {1}", "NumberDecimalDigits", nf.NumberDecimalDigits);
+ writer.WriteLine ("{0}: {1}", "NumberDecimalSeparator", nf.NumberDecimalSeparator);
+ writer.WriteLine ("{0}: {1}", "NumberGroupSeparator", nf.NumberGroupSeparator);
+ Dump (writer, nf.NumberGroupSizes, "NumberGroupSizes", true);
+ writer.WriteLine ("{0}: {1}", "NumberNegativePattern", nf.NumberNegativePattern);
+ writer.WriteLine ("{0}: {1}", "PercentDecimalDigits", nf.PercentDecimalDigits);
+ writer.WriteLine ("{0}: {1}", "PercentDecimalSeparator", nf.PercentDecimalSeparator);
+ writer.WriteLine ("{0}: {1}", "PercentGroupSeparator", nf.PercentGroupSeparator);
+ Dump (writer, nf.PercentGroupSizes, "PercentGroupSizes", true);
+ writer.WriteLine ("{0}: {1}", "PercentNegativePattern", nf.PercentNegativePattern);
+ writer.WriteLine ("{0}: {1}", "PercentPositivePattern", nf.PercentPositivePattern);
+ writer.WriteLine ("{0}: {1}", "PercentSymbol", nf.PercentSymbol);
+ writer.WriteLine ("{0}: {1}", "PerMilleSymbol", nf.PerMilleSymbol);
+ writer.WriteLine ("{0}: {1}", "PositiveInfinitySymbol", nf.PositiveInfinitySymbol);
+ writer.WriteLine ("{0}: {1}", "PositiveSign", nf.PositiveSign);
+
+ if (c.RegionInfoEntry != null) {
+ var ri = c.RegionInfoEntry;
+ writer.WriteLine ("-- RegionInfo --");
+ writer.WriteLine ("{0}: {1}", "CurrencyEnglishName", ri.CurrencyEnglishName);
+ writer.WriteLine ("{0}: {1}", "CurrencyNativeName", ri.CurrencyNativeName);
+ writer.WriteLine ("{0}: {1}", "CurrencySymbol", ri.CurrencySymbol);
+ writer.WriteLine ("{0}: {1}", "DisplayName", ri.DisplayName);
+ writer.WriteLine ("{0}: {1}", "EnglishName", ri.EnglishName);
+ writer.WriteLine ("{0}: {1}", "GeoId", ri.GeoId);
+ writer.WriteLine ("{0}: {1}", "IsMetric", ri.IsMetric);
+ writer.WriteLine ("{0}: {1}", "ISOCurrencySymbol", ri.ISOCurrencySymbol);
+ writer.WriteLine ("{0}: {1}", "Name", ri.Name);
+ writer.WriteLine ("{0}: {1}", "NativeName", ri.NativeName);
+ writer.WriteLine ("{0}: {1}", "ThreeLetterISORegionName", ri.ThreeLetterISORegionName);
+ writer.WriteLine ("{0}: {1}", "ThreeLetterWindowsRegionName", ri.ThreeLetterWindowsRegionName);
+ writer.WriteLine ("{0}: {1}", "TwoLetterISORegionName", ri.TwoLetterISORegionName);
}
- writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
- writer.Write (builder);
- writer.WriteLine ("};\n\n");
- builder = new StringBuilder ();
- rcount = 0;
- foreach (RegionInfoEntry ri in regionList) {
- builder.Append ("\t{" + Entry.EncodeStringIdx (ri.ISO2Name) + ", ");
- builder.Append (ri.RegionId + "}");
- if (++rcount < regionList.Count)
- builder.Append (',');
- builder.Append ('\n');
- }
-
- writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
- writer.Write (builder);
- writer.WriteLine ("};\n\n");
-
- writer.WriteLine ("static const char locale_strings [] = {");
- writer.Write (Entry.GetStrings ());
- writer.WriteLine ("};\n\n");
-
- writer.WriteLine ("#endif\n");
- }
+ writer.WriteLine ();
+ }
}
- private XPathDocument GetXPathDocument (string path)
+ static Type GetCalendarType (CalendarType ct)
{
- XmlTextReader xtr = null;
- try {
- xtr = new XmlTextReader (path);
- xtr.XmlResolver = null;
- return new XPathDocument (xtr);
- } finally {
- if (xtr != null)
- xtr.Close ();
+ switch (ct) {
+ case CalendarType.Gregorian:
+ return typeof (GregorianCalendar);
+ case CalendarType.HijriCalendar:
+ return typeof (HijriCalendar);
+ case CalendarType.ThaiBuddhist:
+ return typeof (ThaiBuddhistCalendar);
+ case CalendarType.UmAlQuraCalendar:
+ return typeof (UmAlQuraCalendar);
+ default:
+ throw new NotImplementedException ();
}
}
- private string GetShortName (string lang)
+ static void Dump<T> (TextWriter tw, IList<T> values, string name, bool stopOnNull = false) where T : class
{
- return lang == "zh-CHS" ? "zh" : lang;
- }
+ tw.Write (name);
+ tw.Write (": ");
- private bool ParseLang (string lang)
- {
- XPathDocument doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
- XPathNavigator nav = doc.CreateNavigator ();
- CultureInfoEntry ci = new CultureInfoEntry ();
- string lang_type, terr_type;
+ for (int i = 0; i < values.Count; ++i) {
+ var v = values[i];
-// ci.Name = lang; // TODO: might need to be mapped.
+ if (stopOnNull && v == null)
+ break;
- lang_type = nav.Evaluate ("string (ldml/identity/language/@type)").ToString ();
- terr_type = nav.Evaluate ("string (ldml/identity/territory/@type)").ToString ();
+ if (i > 0)
+ tw.Write (", ");
- ci.Language = (lang_type == String.Empty ? null : lang_type);
- ci.Territory = (terr_type == String.Empty ? null : terr_type);
+ tw.Write (v);
+ }
- if (!LookupLcids (ci, true))
- return false;
+ tw.WriteLine ();
+ }
- doc = GetXPathDocument (Path.Combine ("langs", GetShortName (Lang) + ".xml"));
- nav = doc.CreateNavigator ();
- ci.DisplayName = LookupFullName (ci, nav);
-
- if (Lang == "en") {
- ci.EnglishName = ci.DisplayName;
- } else {
- doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
- nav = doc.CreateNavigator ();
- ci.EnglishName = LookupFullName (ci, nav);
- }
+ void Run ()
+ {
+ Regex locales_regex = null;
+ if (Locales != null)
+ locales_regex = new Regex (Locales);
- if (ci.Language == Lang) {
- ci.NativeName = ci.DisplayName;
- } else {
- doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
- nav = doc.CreateNavigator ();
- ci.NativeName = LookupFullName (ci, nav);
- }
+ cultures = new List<CultureInfoEntry> ();
+ var regions = new List<RegionInfoEntry> ();
- // Null these out because langs dont have them
- ci.DateTimeFormatEntry = null;
- ci.NumberFormatEntry = null;
- langs [lang] = ci;
- cultures.Add (ci);
+ var supplemental = GetXmlDocument (Path.Combine (data_root, "supplemental", "supplementalData.xml"));
- return true;
- }
+ // Read currencies info
+ region_currency = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
+ foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/currencyData/region")) {
+ var child = entry.SelectSingleNode ("currency");
+ region_currency.Add (entry.Attributes["iso3166"].Value, child.Attributes["iso4217"].Value);
+ }
- private void ParseLocale (string locale)
- {
- CultureInfoEntry ci;
+ var lcdids = GetXmlDocument ("lcids.xml");
+ foreach (XmlNode lcid in lcdids.SelectNodes ("lcids/lcid")) {
+ var name = lcid.Attributes["name"].Value;
- ci = LookupCulture (locale);
+ if (locales_regex != null && !locales_regex.IsMatch (name))
+ continue;
- if (ci == null)
- return;
+ var ci = new CultureInfoEntry ();
+ ci.LCID = lcid.Attributes["id"].Value;
+ ci.ParentLcid = lcid.Attributes["parent"].Value;
+ ci.TwoLetterISOLanguageName = lcid.Attributes["iso2"].Value;
+ ci.ThreeLetterISOLanguageName = lcid.Attributes["iso3"].Value;
+ ci.ThreeLetterWindowsLanguageName = lcid.Attributes["win"].Value;
+ ci.OriginalName = name.Replace ('_', '-');
+ ci.TextInfoEntry = new TextInfoEntry ();
+ ci.NumberFormatEntry = new NumberFormatEntry ();
+
+ if (!Import (ci, name))
+ continue;
- if (langs [GetLanguageFixed (ci)] == null) {
- if (!ParseLang (GetLanguageFixed (ci))) // If we can't parse the lang we cant have the locale
- return;
- }
+ cultures.Add (ci);
+ }
- cultures.Add (ci);
- }
+ var doc_english = GetXmlDocument (Path.Combine (data_root, "main", "en.xml"));
+
+ //
+ // Fill all EnglishName values from en.xml language file
+ //
+ foreach (var ci in cultures) {
+ var el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", ci.Language));
+ if (el != null)
+ ci.EnglishName = el.InnerText;
+
+ string s = null;
+ if (ci.Script != null) {
+ el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/scripts/script[@type='{0}']", ci.Script));
+ if (el != null)
+ s = el.InnerText;
+ }
- private CultureInfoEntry LookupCulture (string locale)
- {
- string path = Path.Combine ("locales", locale + ".xml");
- if (!File.Exists (path))
- return null;
- XPathDocument doc = GetXPathDocument (path);
- XPathNavigator nav = doc.CreateNavigator ();
- CultureInfoEntry ci = new CultureInfoEntry ();
- string supp;
+ if (ci.Territory != null) {
+ el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci.Territory));
+ if (el != null) {
+ if (s == null)
+ s = el.InnerText;
+ else
+ s = string.Join (", ", s, el.InnerText);
+ }
+ }
-// ci.Name = locale; // TODO: Some of these need to be mapped.
+ switch (ci.ThreeLetterWindowsLanguageName) {
+ case "CHT":
+ s = "Traditional";
+ break;
+ case "CHS":
+ s = "Simplified";
+ break;
+ }
- // First thing we do is get the lang-territory combo, lcid, and full names
- ci.Language = nav.Evaluate ("string (ldml/identity/language/@type)").ToString ();
- ci.Territory = nav.Evaluate ("string (ldml/identity/territory/@type)").ToString ();
+ if (s != null)
+ ci.EnglishName = string.Format ("{0} ({1})", ci.EnglishName, s);
- if (!LookupLcids (ci, false))
- return null;
- LookupNames (ci);
+ // Special case legacy chinese
+ if (ci.OriginalName == "zh-CHS" || ci.OriginalName == "zh-CHT")
+ ci.EnglishName += " Legacy";
- /**
- * Locale generation is done in six steps, first we
- * read the root file which is the base invariant data
- * then the supplemental root data,
- * then the language file, the supplemental languages
- * file then the locale file, then the supplemental
- * locale file. Values in each descending file can
- * overwrite previous values.
- */
- doc = GetXPathDocument (Path.Combine ("langs", "root.xml"));
- nav = doc.CreateNavigator ();
- Lookup (nav, ci);
-
- doc = GetXPathDocument (Path.Combine ("supp", "root.xml"));
- nav = doc.CreateNavigator ();
- Lookup (nav, ci);
-
- doc = GetXPathDocument (Path.Combine ("langs", GetShortName (GetLanguageFixed (ci)) + ".xml"));
- nav = doc.CreateNavigator ();
- Lookup (nav, ci);
-
- supp = Path.Combine ("supp", GetLanguageFixed (ci) + ".xml");
- if (File.Exists (supp)) {
- doc = GetXPathDocument (supp);
- nav = doc.CreateNavigator ();
- Lookup (nav, ci);
+ // Mono is not localized and supports english only, hence the name will always be same
+ ci.DisplayName = ci.EnglishName;
}
-
- doc = GetXPathDocument (Path.Combine ("locales", locale + ".xml"));
- nav = doc.CreateNavigator ();
- Lookup (nav, ci);
-
- supp = Path.Combine ("supp", locale + ".xml");
- if (File.Exists (supp)) {
- doc = GetXPathDocument (supp);
- nav = doc.CreateNavigator ();
- Lookup (nav, ci);
+
+ //
+ // Fill culture hierarchy for easier data manipulation
+ //
+ foreach (var ci in cultures) {
+ foreach (var p in cultures.Where (l => ci.LCID == l.ParentLcid)) {
+ ci.Children.Add (p);
+ }
}
- return ci;
- }
+ currency_fractions = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
+ foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/currencyData/fractions/info")) {
+ currency_fractions.Add (entry.Attributes["iso4217"].Value, entry.Attributes["digits"].Value);
+ }
- private void Lookup (XPathNavigator nav, CultureInfoEntry ci)
- {
- LookupDateTimeInfo (nav, ci);
- LookupNumberInfo (nav, ci);
- }
+ var territory2dayofweek = new Dictionary<string, DayOfWeek> (StringComparer.OrdinalIgnoreCase);
+ foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/firstDay")) {
+ DayOfWeek dow;
- private string GetLanguageFixed (CultureInfoEntry ci)
- {
- // This is a hack, but without it nb-NO and nn-NO won't work.
- if (ci.Territory == "NO") {
- switch (ci.Language) {
- case "nb":
- case "nn":
- return "no";
+ switch (entry.Attributes["day"].Value) {
+ case "mon":
+ dow = DayOfWeek.Monday;
+ break;
+ case "fri":
+ dow = DayOfWeek.Friday;
+ break;
+ case "sat":
+ dow = DayOfWeek.Saturday;
+ break;
+ case "sun":
+ dow = DayOfWeek.Sunday;
+ break;
+ default:
+ throw new NotImplementedException ();
}
+
+ var territories = entry.Attributes["territories"].Value.Split ();
+ foreach (var t in territories)
+ territory2dayofweek[t] = dow;
}
- return ci.Language;
- }
- private void LookupNames (CultureInfoEntry ci)
- {
- XPathDocument doc = GetXPathDocument (Path.Combine ("langs", GetShortName (Lang) + ".xml"));
- XPathNavigator nav = doc.CreateNavigator ();
+ var territory2wr = new Dictionary<string, CalendarWeekRule> (StringComparer.OrdinalIgnoreCase);
+ foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/minDays")) {
+ CalendarWeekRule rule;
- ci.DisplayName = LookupFullName (ci, nav);
-
- if (Lang == "en") {
- ci.EnglishName = ci.DisplayName;
- } else {
- doc = GetXPathDocument (Path.Combine ("langs", "en.xml"));
- nav = doc.CreateNavigator ();
- ci.EnglishName = LookupFullName (ci, nav);
+ switch (entry.Attributes["count"].InnerText) {
+ case "1":
+ rule = CalendarWeekRule.FirstDay;
+ break;
+ case "4":
+ rule = CalendarWeekRule.FirstFourDayWeek;
+ break;
+ default:
+ throw new NotImplementedException ();
+ }
+
+ var territories = entry.Attributes["territories"].InnerText.Split ();
+ foreach (var t in territories)
+ territory2wr[t] = rule;
}
- if (ci.Language == Lang) {
- ci.NativeName = ci.DisplayName;
- } else {
- // FIXME: We use ci.Language here.
- // This is nothing more than hack for nb-NO and nn-NO
- // where Parent of them is nn (not nb or nn).
- string lang = ci.Language;
- doc = GetXPathDocument (Path.Combine ("langs", GetShortName (lang) + ".xml"));
- nav = doc.CreateNavigator ();
- ci.NativeName = LookupFullName (ci, nav);
+ //
+ // Fill all territory speficic data where territory is available
+ //
+ var non_metric = new HashSet<string> ();
+ foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/measurementData/measurementSystem[@type='US']")) {
+ var territories = entry.Attributes["territories"].InnerText.Split ();
+ foreach (var t in territories)
+ non_metric.Add (t);
}
- }
- private void AddPattern (ArrayList al, string pattern)
- {
- if (!al.Contains (pattern))
- al.Add (pattern);
- }
+ foreach (var ci in cultures) {
+ if (ci.Territory == null)
+ continue;
- private void LookupDateTimeInfo (XPathNavigator nav, CultureInfoEntry ci)
- {
- /**
- * TODO: Does anyone have multiple calendars?
- */
- XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("ldml/dates/calendars/calendar");
-
- while (ni.MoveNext ()) {
- DateTimeFormatEntry df = ci.DateTimeFormatEntry;
- string cal_type = ni.Current.GetAttribute ("type", String.Empty);
-
- if (cal_type != String.Empty)
- df.CalendarType = cal_type;
-
- XPathNodeIterator ni2 = (XPathNodeIterator) ni.Current.Evaluate ("optionalCalendars/calendar");
- int opt_cal_count = 0;
- while (ni2.MoveNext ()) {
- int type;
- string greg_type_str;
- XPathNavigator df_nav = ni2.Current;
- switch (df_nav.GetAttribute ("type", String.Empty)) {
- case "Gregorian":
- type = 0;
- break;
- case "Hijri":
- type = 0x01;
- break;
- case "ThaiBuddhist":
- type = 0x02;
- break;
- default:
- Console.WriteLine ("unknown calendar type: " +
- df_nav.GetAttribute ("type", String.Empty));
- continue;
- }
- type <<= 24;
- greg_type_str = df_nav.GetAttribute ("greg_type", String.Empty);
- if (greg_type_str != null && greg_type_str != String.Empty) {
- GregorianCalendarTypes greg_type = (GregorianCalendarTypes)
- Enum.Parse (typeof (GregorianCalendarTypes), greg_type_str);
- int greg_type_int = (int) greg_type;
- type |= greg_type_int;
-
- }
- Console.WriteLine ("Setting cal type: {0:X} for {1}", type, ci.Name);
- ci.CalendarData [opt_cal_count++] = type;
- }
-
- ni2 = (XPathNodeIterator) ni.Current.Evaluate ("monthNames/month");
- while (ni2.MoveNext ()) {
- if (ni2.CurrentPosition == 1)
- df.MonthNames.Clear ();
- df.MonthNames.Add (ni2.Current.Value);
+ DayOfWeek value;
+ if (territory2dayofweek.TryGetValue (ci.Territory, out value)) {
+ ci.DateTimeFormatEntry.FirstDayOfWeek = (int) value;
}
- ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dayNames/day");
- while (ni2.MoveNext ()) {
- if (ni2.CurrentPosition == 1)
- df.DayNames.Clear ();
- df.DayNames.Add (ni2.Current.Value);
+ CalendarWeekRule rule;
+ if (territory2wr.TryGetValue (ci.Territory, out rule)) {
+ ci.DateTimeFormatEntry.CalendarWeekRule = (int) rule;
}
- ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dayAbbr/day");
- while (ni2.MoveNext ()) {
- if (ni2.CurrentPosition == 1)
- df.AbbreviatedDayNames.Clear ();
- df.AbbreviatedDayNames.Add (ni2.Current.Value);
+ string fraction_value;
+ if (currency_fractions.TryGetValue (ci.Territory, out fraction_value)) {
+ ci.NumberFormatEntry.CurrencyDecimalDigits = fraction_value;
}
- ni2 = (XPathNodeIterator) ni.Current.Evaluate ("monthAbbr/month");
- while (ni2.MoveNext ()) {
- if (ni2.CurrentPosition == 1)
- df.AbbreviatedMonthNames.Clear ();
- df.AbbreviatedMonthNames.Add (ni2.Current.Value);
+ RegionInfoEntry region = regions.Where (l => l.Name == ci.Territory).FirstOrDefault ();
+ if (region == null) {
+ region = new RegionInfoEntry () {
+ CurrencySymbol = ci.NumberFormatEntry.CurrencySymbol,
+ EnglishName = ci.EnglishName,
+ NativeName = ci.NativeTerritoryName,
+ Name = ci.Territory,
+ TwoLetterISORegionName = ci.Territory,
+ CurrencyNativeName = ci.NativeCurrencyName
+ };
+
+ var tc = supplemental.SelectSingleNode (string.Format ("supplementalData/codeMappings/territoryCodes[@type='{0}']", ci.Territory));
+ region.ThreeLetterISORegionName = tc.Attributes["alpha3"].Value;
+ region.ThreeLetterWindowsRegionName = region.ThreeLetterISORegionName;
+
+ var el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci.Territory));
+ region.EnglishName = el.InnerText;
+ region.DisplayName = region.EnglishName;
+
+ region.ISOCurrencySymbol = region_currency[ci.Territory];
+
+ el = doc_english.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", region.ISOCurrencySymbol));
+ region.CurrencyEnglishName = el.InnerText;
+
+ if (non_metric.Contains (ci.Territory))
+ region.IsMetric = false;
+
+ var lcdid_value = int.Parse (ci.LCID.Substring (2), NumberStyles.HexNumber);
+ Patterns.FillValues (lcdid_value, region);
+ regions.Add (region);
}
- ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dateFormats/dateFormatLength");
- while (ni2.MoveNext ()) {
- XPathNavigator df_nav = ni2.Current;
- XPathNodeIterator p = df_nav.Select ("dateFormat/pattern");
- string value = null;
- if (p.MoveNext ())
- value = p.Current.Value;
- XPathNodeIterator ext = null;
- switch (df_nav.GetAttribute ("type", String.Empty)) {
- case "full":
- if (value != null)
- ParseFullDateFormat (df, value);
+ ci.RegionInfoEntry = region;
+ }
+
+ //
+ // Fill neutral cultures territory data
+ //
+ foreach (var ci in cultures) {
+ var dtf = ci.DateTimeFormatEntry;
+ if (dtf.FirstDayOfWeek == null) {
+ switch (ci.Name) {
+ case "ar":
+ dtf.FirstDayOfWeek = (int) DayOfWeek.Saturday;
break;
- case "long":
- if (value != null)
- df.LongDatePattern = value;
- ext = df_nav.Select ("extraPatterns/pattern");
- if (ext.MoveNext ()) {
- df.LongDatePatterns.Clear ();
- AddPattern (df.LongDatePatterns, df.LongDatePattern);
- do {
- df.LongDatePatterns.Add (ext.Current.Value);
- } while (ext.MoveNext ());
- }
- else
- AddPattern (df.LongDatePatterns, df.LongDatePattern);
+ case "en":
+ case "pt":
+ case "zh-Hans":
+ dtf.FirstDayOfWeek = (int) DayOfWeek.Sunday;
+ break;
+ case "es":
+ case "fr":
+ case "bn":
+ case "sr-Cyrl":
+ case "sr-Latn":
+ dtf.FirstDayOfWeek = (int) DayOfWeek.Monday;
break;
- case "short":
- if (value != null)
- df.ShortDatePattern = value;
- ext = df_nav.Select ("extraPatterns/pattern");
- if (ext.MoveNext ()) {
- df.ShortDatePatterns.Clear ();
- AddPattern (df.ShortDatePatterns, df.ShortDatePattern);
- do {
- df.ShortDatePatterns.Add (ext.Current.Value);
- } while (ext.MoveNext ());
+ default:
+ List<int?> all_fdow = new List<int?> ();
+ GetAllChildrenValues (ci, all_fdow, l => l.DateTimeFormatEntry.FirstDayOfWeek);
+ var children = all_fdow.Where (l => l != null).Distinct ().ToList ();
+
+ if (children.Count == 1) {
+ dtf.FirstDayOfWeek = children[0];
+ } else if (children.Count == 0) {
+ if (!ci.HasMissingLocale)
+ Console.WriteLine ("No week data for `{0}'", ci.Name);
+
+ // Default to Sunday
+ dtf.FirstDayOfWeek = (int) DayOfWeek.Sunday;
+ } else {
+ // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
+ // We have to manually disambiguate the correct entry (which is artofficial anyway)
+ throw new ApplicationException (string.Format ("Ambiguous week data for `{0}'", ci.Name));
}
- else
- AddPattern (df.ShortDatePatterns, df.ShortDatePattern);
+
break;
- case "year_month":
- if (value != null)
- df.YearMonthPattern = value;
+ }
+ }
+
+ if (dtf.CalendarWeekRule == null) {
+ switch (ci.Name) {
+ case "ar":
+ case "en":
+ case "es":
+ case "zh-Hans":
+ case "pt":
+ case "fr":
+ case "bn":
+ dtf.CalendarWeekRule = (int) CalendarWeekRule.FirstDay;
break;
- case "month_day":
- if (value != null)
- df.MonthDayPattern = value;
+ default:
+ List<int?> all_cwr = new List<int?> ();
+ GetAllChildrenValues (ci, all_cwr, l => l.DateTimeFormatEntry.CalendarWeekRule);
+ var children = all_cwr.Where (l => l != null).Distinct ().ToList ();
+
+ if (children.Count == 1) {
+ dtf.CalendarWeekRule = children[0];
+ } else if (children.Count == 0) {
+ if (!ci.HasMissingLocale)
+ Console.WriteLine ("No calendar week data for `{0}'", ci.Name);
+
+
+ // Default to FirstDay
+ dtf.CalendarWeekRule = (int) CalendarWeekRule.FirstDay;
+ } else {
+ // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
+ // We have to manually disambiguate the correct entry (which is artofficial anyway)
+ throw new ApplicationException (string.Format ("Ambiguous calendar data for `{0}'", ci.Name));
+ }
+
break;
}
}
- ni2 = (XPathNodeIterator) ni.Current.Evaluate ("timeFormats/timeFormatLength");
- while (ni2.MoveNext ()) {
- XPathNavigator df_nav = ni2.Current;
- XPathNodeIterator p = df_nav.Select ("timeFormat/pattern");
- string value = null;
- if (p.MoveNext ())
- value = p.Current.Value;
- XPathNodeIterator ext = null;
- switch (df_nav.GetAttribute ("type", String.Empty)) {
- case "long":
- if (value != null)
- df.LongTimePattern = value.Replace ('a', 't');
- ext = df_nav.Select ("extraPatterns/pattern");
- if (ext.MoveNext ()) {
- df.LongTimePatterns.Clear ();
- AddPattern (df.LongTimePatterns, df.LongTimePattern);
- do {
- df.LongTimePatterns.Add (ext.Current.Value);
- } while (ext.MoveNext ());
- }
- else
- AddPattern (df.LongTimePatterns, df.LongTimePattern);
+ var nfe = ci.NumberFormatEntry;
+ if (nfe.CurrencySymbol == null) {
+ switch (ci.Name) {
+ case "ar":
+ nfe.CurrencySymbol = "ر.س.";
+ break;
+ case "en":
+ nfe.CurrencySymbol = "$";
break;
- case "short":
- if (value != null)
- df.ShortTimePattern = value.Replace ('a', 't');
- ext = df_nav.Select ("extraPatterns/pattern");
- if (ext.MoveNext ()) {
- df.ShortTimePatterns.Clear ();
- AddPattern (df.ShortTimePatterns, df.ShortTimePattern);
- do {
- df.ShortTimePatterns.Add (ext.Current.Value);
- } while (ext.MoveNext ());
+ case "es":
+ case "fr":
+ nfe.CurrencySymbol = "€";
+ break;
+ case "pt":
+ nfe.CurrencySymbol = "R$";
+ break;
+ case "sv":
+ nfe.CurrencySymbol = "kr";
+ break;
+ case "ms":
+ nfe.CurrencySymbol = "RM";
+ break;
+ case "bn":
+ nfe.CurrencySymbol = "টা";
+ break;
+ case "sr-Cyrl":
+ nfe.CurrencySymbol = "Дин.";
+ break;
+ case "sr-Latn":
+ case "sr":
+ nfe.CurrencySymbol = "Din.";
+ break;
+ case "zh":
+ nfe.CurrencySymbol = "¥";
+ break;
+ case "zh-Hant":
+ nfe.CurrencySymbol = "HK$";
+ break;
+
+ default:
+ var all_currencies = new List<string> ();
+ GetAllChildrenValues (ci, all_currencies, l => l.NumberFormatEntry.CurrencySymbol);
+ var children = all_currencies.Where (l => l != null).Distinct ().ToList ();
+
+ if (children.Count == 1) {
+ nfe.CurrencySymbol = children[0];
+ } else if (children.Count == 0) {
+ if (!ci.HasMissingLocale)
+ Console.WriteLine ("No currency data for `{0}'", ci.Name);
+
+
+ } else {
+ // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
+ // We have to manually disambiguate the correct entry (which is artofficial anyway)
+ throw new ApplicationException (string.Format ("Ambiguous currency data for `{0}'", ci.Name));
}
- else
- AddPattern (df.ShortTimePatterns, df.ShortTimePattern);
+
break;
}
}
- ni2 = (XPathNodeIterator) ni.Current.Evaluate ("dateTimeFormats/dateTimeFormatLength/dateTimeFormat/pattern");
- if (ni2.MoveNext ())
- df.FullDateTimePattern = String.Format (ni2.Current.ToString (),
- df.LongTimePattern, df.LongDatePattern);
-
- XPathNodeIterator am = ni.Current.SelectChildren ("am", "");
- if (am.MoveNext ())
- df.AMDesignator = am.Current.Value;
- XPathNodeIterator pm = ni.Current.SelectChildren ("pm", "");
- if (pm.MoveNext ())
- df.PMDesignator = pm.Current.Value;
-/*
- string am = (string) ni.Current.Evaluate ("string(am)");
- string pm = (string) ni.Current.Evaluate ("string(pm)");
+ if (nfe.CurrencyDecimalDigits == null) {
+ var all_digits = new List<string> ();
+ GetAllChildrenValues (ci, all_digits, l => l.NumberFormatEntry.CurrencyDecimalDigits);
+ var children = all_digits.Where (l => l != null).Distinct ().ToList ();
- if (am != String.Empty)
- df.AMDesignator = am;
- if (pm != String.Empty)
- df.PMDesignator = pm;
-*/
+ if (children.Count == 1) {
+ nfe.CurrencyDecimalDigits = children[0];
+ } else if (children.Count == 0) {
+ if (!ci.HasMissingLocale)
+ Console.WriteLine ("No currency decimal digits data for `{0}'", ci.Name);
+
+ nfe.CurrencyDecimalDigits = "2";
+ } else {
+ // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
+ // We have to manually disambiguate the correct entry (which is artofficial anyway)
+ throw new ApplicationException (string.Format ("Ambiguous currency decimal digits data for `{0}'", ci.Name));
+ }
+ }
}
- string date_sep = (string) nav.Evaluate ("string(ldml/dates/symbols/dateSeparator)");
- string time_sep = (string) nav.Evaluate ("string(ldml/dates/symbols/timeSeparator)");
+ if (OutputCompare)
+ Print ();
- if (date_sep != String.Empty)
- ci.DateTimeFormatEntry.DateSeparator = date_sep;
- if (time_sep != String.Empty)
- ci.DateTimeFormatEntry.TimeSeparator = time_sep;
- }
+ regions.Sort (new RegionComparer ());
+ for (int i = 0; i < regions.Count; ++i)
+ regions[i].Index = i;
- private void LookupNumberInfo (XPathNavigator nav, CultureInfoEntry ci)
- {
- XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("ldml/numbers");
-
- while (ni.MoveNext ()) {
- LookupNumberSymbols (ni.Current, ci);
- LookupDecimalFormat (ni.Current, ci);
- LookupPercentFormat (ni.Current, ci);
- LookupCurrencyFormat (ni.Current, ci);
- LookupCurrencySymbol (ni.Current, ci);
+ /**
+ * Dump each table individually. Using StringBuilders
+ * because it is easier to debug, should switch to just
+ * writing to streams eventually.
+ */
+ using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
+ writer.NewLine = "\n";
+ writer.WriteLine ();
+ writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
+ writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
+ writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
+ writer.WriteLine ("\n");
+
+ writer.WriteLine ("#define NUM_CULTURE_ENTRIES {0}", cultures.Count);
+ writer.WriteLine ("#define NUM_REGION_ENTRIES {0}", regions.Count);
+
+ writer.WriteLine ("\n");
+
+ // Sort the cultures by lcid
+ cultures.Sort (new LcidComparer ());
+
+ StringBuilder builder = new StringBuilder ();
+ int row = 0;
+ int count = cultures.Count;
+ for (int i = 0; i < count; i++) {
+ CultureInfoEntry ci = cultures[i];
+ if (ci.DateTimeFormatEntry == null)
+ continue;
+ ci.DateTimeFormatEntry.AppendTableRow (builder);
+ ci.DateTimeFormatEntry.Row = row++;
+ if (i + 1 < count)
+ builder.Append (',');
+ builder.Append ('\n');
+ }
+
+ writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
+ writer.Write (builder);
+ writer.WriteLine ("};\n\n");
+
+ builder = new StringBuilder ();
+ row = 0;
+ for (int i = 0; i < count; i++) {
+ CultureInfoEntry ci = cultures[i];
+ if (ci.NumberFormatEntry == null)
+ continue;
+ ci.NumberFormatEntry.AppendTableRow (builder);
+ ci.NumberFormatEntry.Row = row++;
+ if (i + 1 < count)
+ builder.Append (',');
+ builder.Append ('\n');
+ }
+
+ writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
+ writer.Write (builder);
+ writer.WriteLine ("};\n\n");
+
+ builder = new StringBuilder ();
+ row = 0;
+ for (int i = 0; i < count; i++) {
+ CultureInfoEntry ci = cultures[i];
+ ci.AppendTableRow (builder);
+ ci.Row = row++;
+ if (i + 1 < count)
+ builder.Append (',');
+ builder.Append ('\n');
+ }
+
+ writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
+ writer.Write (builder);
+ writer.WriteLine ("};\n\n");
+
+ cultures.Sort (new ExportNameComparer ()); // Sort based on name
+ builder = new StringBuilder ();
+ for (int i = 0; i < count; i++) {
+ CultureInfoEntry ci = cultures[i];
+ var name = ci.GetExportName ().ToLowerInvariant ();
+ builder.Append ("\t{" + Entry.EncodeStringIdx (name) + ", ");
+ builder.Append (ci.Row + "}");
+ if (i + 1 < count)
+ builder.Append (',');
+
+ builder.AppendFormat ("\t /* {0} */", name);
+ builder.Append ('\n');
+ }
+
+ writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
+ writer.Write (builder);
+ writer.WriteLine ("};\n\n");
+
+ builder = new StringBuilder ();
+ int rcount = 0;
+ foreach (RegionInfoEntry r in regions) {
+ r.AppendTableRow (builder);
+ if (++rcount != regions.Count)
+ builder.Append (',');
+
+ builder.Append ('\n');
+ }
+ writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
+ writer.Write (builder);
+ writer.WriteLine ("};\n\n");
+
+ builder = new StringBuilder ();
+ rcount = 0;
+ foreach (RegionInfoEntry ri in regions) {
+ builder.Append ("\t{" + Entry.EncodeStringIdx (ri.TwoLetterISORegionName) + ", ");
+ builder.Append (ri.Index + "}");
+ if (++rcount != regions.Count)
+ builder.Append (',');
+
+ builder.AppendFormat ("\t /* {0} */", ri.TwoLetterISORegionName);
+ builder.Append ('\n');
+ }
+
+ writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
+ writer.Write (builder);
+ writer.WriteLine ("};\n\n");
+
+ writer.WriteLine ("static const char locale_strings [] = {");
+ writer.Write (Entry.GetStrings ());
+ writer.WriteLine ("};\n\n");
+
+ writer.WriteLine ("#endif\n");
}
}
- private void LookupDecimalFormat (XPathNavigator nav, CultureInfoEntry ci)
+ static void GetAllChildrenValues<T> (CultureInfoEntry entry, List<T> values, Func<CultureInfoEntry, T> selector)
{
- string format = (string) nav.Evaluate ("string(decimalFormats/" +
- "decimalFormatLength/decimalFormat/pattern)");
-
- if (format == String.Empty)
- return;
-
- string [] part_one, part_two;
- string [] pos_neg = format.Split (new char [1] {';'}, 2);
-
- // Most of the patterns are common in positive and negative
- if (pos_neg.Length == 1)
- pos_neg = new string [] {pos_neg [0], pos_neg [0]};
-
- if (pos_neg.Length == 2) {
-
- part_one = pos_neg [0].Split (new char [1] {'.'}, 2);
- if (part_one.Length == 1)
- part_one = new string [] {part_one [0], String.Empty};
-
- if (part_one.Length == 2) {
- // assumed same for both positive and negative
- // decimal digit side
- ci.NumberFormatEntry.NumberDecimalDigits = 0;
- for (int i = 0; i < part_one [1].Length; i++) {
- if (part_one [1][i] == '#') {
- ci.NumberFormatEntry.NumberDecimalDigits ++;
- } else
- break; }
- // FIXME: This should be actually done by modifying culture xml files, but too many files to be modified.
- if (ci.NumberFormatEntry.NumberDecimalDigits > 0)
- ci.NumberFormatEntry.NumberDecimalDigits --;
-
- // decimal grouping side
- part_two = part_one [0].Split (',');
- if (part_two.Length > 1) {
- int len = part_two.Length - 1;
- ci.NumberFormatEntry.NumberGroupSizes = new int [len];
- for (int i = 0; i < len; i++) {
- string pat = part_two [i + 1];
- ci.NumberFormatEntry.NumberGroupSizes [i] = pat.Length;
- }
- } else {
- ci.NumberFormatEntry.NumberGroupSizes = new int [1] { 3 };
- }
+ foreach (var e in entry.Children) {
+ if (e == entry)
+ continue;
- if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith (")")) {
- ci.NumberFormatEntry.NumberNegativePattern = 0;
- } else if (pos_neg [1].StartsWith ("- ")) {
- ci.NumberFormatEntry.NumberNegativePattern = 2;
- } else if (pos_neg [1].StartsWith ("-")) {
- ci.NumberFormatEntry.NumberNegativePattern = 1;
- } else if (pos_neg [1].EndsWith (" -")) {
- ci.NumberFormatEntry.NumberNegativePattern = 4;
- } else if (pos_neg [1].EndsWith ("-")) {
- ci.NumberFormatEntry.NumberNegativePattern = 3;
- } else {
- ci.NumberFormatEntry.NumberNegativePattern = 1;
- }
+ values.Add (selector (e));
+
+ foreach (var e2 in e.Children) {
+ GetAllChildrenValues (e2, values, selector);
}
}
}
- private void LookupPercentFormat (XPathNavigator nav, CultureInfoEntry ci)
+ static XmlDocument GetXmlDocument (string path)
{
- string format = (string) nav.Evaluate ("string(percentFormats/" +
- "percentFormatLength/percentFormat/pattern)");
-
- if (format == String.Empty)
- return;
-
- string [] part_one, part_two;
-
- // we don't have percentNegativePattern in CLDR so
- // the percentNegativePattern are just guesses
- if (format.StartsWith ("%")) {
- ci.NumberFormatEntry.PercentPositivePattern = 2;
- ci.NumberFormatEntry.PercentNegativePattern = 2;
- format = format.Substring (1);
- } else if (format.EndsWith (" %")) {
- ci.NumberFormatEntry.PercentPositivePattern = 0;
- ci.NumberFormatEntry.PercentNegativePattern = 0;
- format = format.Substring (0, format.Length - 2);
- } else if (format.EndsWith ("%")) {
- ci.NumberFormatEntry.PercentPositivePattern = 1;
- ci.NumberFormatEntry.PercentNegativePattern = 1;
- format = format.Substring (0, format.Length - 1);
- } else {
- ci.NumberFormatEntry.PercentPositivePattern = 0;
- ci.NumberFormatEntry.PercentNegativePattern = 0;
- }
+ var doc = new XmlDocument ();
+ doc.Load (new XmlTextReader (path) { /*DtdProcessing = DtdProcessing.Ignore*/ } );
+ return doc;
+ }
- part_one = format.Split (new char [1] {'.'}, 2);
- if (part_one.Length == 2) {
- // assumed same for both positive and negative
- // decimal digit side
- ci.NumberFormatEntry.PercentDecimalDigits = 0;
- for (int i = 0; i < part_one [1].Length; i++) {
- if (part_one [1][i] == '#')
- ci.NumberFormatEntry.PercentDecimalDigits++;
- else
- break;
+ bool Import (CultureInfoEntry data, string locale)
+ {
+ string fname = null;
+ var sep = locale.Split ('_');
+ data.Language = sep[0];
+
+ // CLDR strictly follow ISO names, .NET does not
+ // Replace names where non-iso2 is used, e.g. Norway
+ if (data.Language != data.TwoLetterISOLanguageName) {
+ locale = data.TwoLetterISOLanguageName;
+ if (sep.Length > 1) {
+ locale += string.Join ("_", sep.Skip (1));
}
}
- if (part_one.Length > 0) {
- // percent grouping side
- part_two = part_one [0].Split (',');
- if (part_two.Length > 1) {
- int len = part_two.Length - 1;
- ci.NumberFormatEntry.PercentGroupSizes = new int [len];
- for (int i = 0; i < len; i++) {
- string pat = part_two [i + 1];
- if (pat [pat.Length -1] == '0')
- ci.NumberFormatEntry.PercentDecimalDigits = pat.Length - 1;
- ci.NumberFormatEntry.PercentGroupSizes [i] = pat.Length;
- }
- } else {
- ci.NumberFormatEntry.PercentGroupSizes = new int [1] { 3 };
- ci.NumberFormatEntry.PercentDecimalDigits = 2;
- }
+ // Convert broken Chinese names to correct one
+ switch (locale) {
+ case "zh_CHS":
+ locale = "zh_Hans";
+ break;
+ case "zh_CHT":
+ locale = "zh_Hant";
+ break;
+ case "zh_CN":
+ locale = "zh_Hans_CN";
+ break;
+ case "zh_HK":
+ locale = "zh_Hant_HK";
+ break;
+ case "zh_SG":
+ locale = "zh_Hans_SG";
+ break;
+ case "zh_TW":
+ locale = "zh_Hant_TW";
+ break;
+ case "zh_MO":
+ locale = "zh_Hant_MO";
+ break;
}
- }
- private void LookupCurrencyFormat (XPathNavigator nav, CultureInfoEntry ci)
- {
- string format = (string) nav.Evaluate ("string(currencyFormats/" +
- "currencyFormatLength/currencyFormat/pattern)");
-
- if (format == String.Empty)
- return;
-
- string [] part_one, part_two;
- string [] pos_neg = format.Split (new char [1] {';'}, 2);
-
- // Most of the patterns are common in positive and negative
- if (pos_neg.Length == 1)
- pos_neg = new string [] {pos_neg [0], pos_neg [0]};
-
- if (pos_neg.Length == 2) {
- part_one = pos_neg [0].Split (new char [1] {'.'}, 2);
- if (part_one.Length == 1)
- part_one = new string [] {part_one [0], String.Empty};
- if (part_one.Length == 2) {
- // assumed same for both positive and negative
- // decimal digit side
- ci.NumberFormatEntry.CurrencyDecimalDigits = 0;
- for (int i = 0; i < part_one [1].Length; i++) {
- if (part_one [1][i] == '0')
- ci.NumberFormatEntry.CurrencyDecimalDigits++;
- else
- break;
- }
+ sep = locale.Split ('_');
- // decimal grouping side
- part_two = part_one [0].Split (',');
- if (part_two.Length > 1) {
- int len = part_two.Length - 1;
- ci.NumberFormatEntry.CurrencyGroupSizes = new int [len];
- for (int i = 0; i < len; i++) {
- string pat = part_two [i + 1];
- ci.NumberFormatEntry.CurrencyGroupSizes [i] = pat.Length;
- }
- } else {
- ci.NumberFormatEntry.CurrencyGroupSizes = new int [1] { 3 };
- }
+ string full_name = Path.Combine (data_root, "main", locale + ".xml");
+ if (!File.Exists (full_name)) {
+ Console.WriteLine ("Missing locale file for `{0}'", locale);
- if (pos_neg [1].StartsWith ("(\u00a4 ") && pos_neg [1].EndsWith (")")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 14;
- } else if (pos_neg [1].StartsWith ("(\u00a4") && pos_neg [1].EndsWith (")")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 0;
- } else if (pos_neg [1].StartsWith ("\u00a4 ") && pos_neg [1].EndsWith ("-")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 11;
- } else if (pos_neg [1].StartsWith ("\u00a4") && pos_neg [1].EndsWith ("-")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 3;
- } else if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith (" \u00a4")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 15;
- } else if (pos_neg [1].StartsWith ("(") && pos_neg [1].EndsWith ("\u00a4")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 4;
- } else if (pos_neg [1].StartsWith ("-") && pos_neg [1].EndsWith (" \u00a4")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 8;
- } else if (pos_neg [1].StartsWith ("-") && pos_neg [1].EndsWith ("\u00a4")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 5;
- } else if (pos_neg [1].StartsWith ("-\u00a4 ")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 9;
- } else if (pos_neg [1].StartsWith ("-\u00a4")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 1;
- } else if (pos_neg [1].StartsWith ("\u00a4 -")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 12;
- } else if (pos_neg [1].StartsWith ("\u00a4-")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 2;
- } else if (pos_neg [1].EndsWith (" \u00a4-")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 10;
- } else if (pos_neg [1].EndsWith ("\u00a4-")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 7;
- } else if (pos_neg [1].EndsWith ("- \u00a4")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 13;
- } else if (pos_neg [1].EndsWith ("-\u00a4")) {
- ci.NumberFormatEntry.CurrencyNegativePattern = 6;
- } else {
- ci.NumberFormatEntry.CurrencyNegativePattern = 0;
+ // We could fill default values but that's not as simple as it seems. For instance for non-neutral
+ // cultures the next part could be territory or not.
+ return false;
+ } else {
+ XmlDocument doc = null;
+
+ /*
+ * Locale generation is done in several steps, first we
+ * read the root file which is the base invariant data
+ * then the supplemental root data,
+ * then the language file, the supplemental languages
+ * file then the locale file, then the supplemental
+ * locale file. Values in each descending file can
+ * overwrite previous values.
+ */
+ foreach (var part in sep) {
+ if (fname != null)
+ fname += "_";
+
+ fname += part;
+
+ var xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
+ if (doc == null)
+ doc = xml;
+
+ Import (xml, data);
+ }
+
+ //
+ // Extract localized locale name from language xml file. Have to do it after both language and territory are read
+ //
+ var el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", data.Language));
+ if (el != null)
+ data.NativeName = el.InnerText;
+
+ if (data.Territory != null) {
+ el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", data.Territory));
+ if (el != null) {
+ // TODO: Should read <localePattern>
+ data.NativeName = string.Format ("{0} ({1})", data.NativeName, el.InnerText);
+ data.NativeTerritoryName = el.InnerText;
}
-
- if (pos_neg [0].StartsWith ("\u00a4 ")) {
- ci.NumberFormatEntry.CurrencyPositivePattern = 2;
- } else if (pos_neg [0].StartsWith ("\u00a4")) {
- ci.NumberFormatEntry.CurrencyPositivePattern = 0;
- } else if (pos_neg [0].EndsWith (" \u00a4")) {
- ci.NumberFormatEntry.CurrencyPositivePattern = 3;
- } else if (pos_neg [0].EndsWith ("\u00a4")) {
- ci.NumberFormatEntry.CurrencyPositivePattern = 1;
- } else {
- ci.NumberFormatEntry.CurrencyPositivePattern = 0;
+
+ string currency;
+ // We have territory now we have to run the process again to extract currency symbol
+ if (region_currency.TryGetValue (data.Territory, out currency)) {
+ fname = null;
+
+ var xml = GetXmlDocument (Path.Combine (data_root, "main", "root.xml"));
+ el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
+ if (el != null)
+ data.NumberFormatEntry.CurrencySymbol = el.InnerText;
+
+ foreach (var part in sep) {
+ if (fname != null)
+ fname += "_";
+
+ fname += part;
+
+ xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
+ el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
+ if (el != null)
+ data.NumberFormatEntry.CurrencySymbol = el.InnerText;
+
+ el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", currency));
+ if (el != null)
+ data.NativeCurrencyName = el.InnerText;
+ }
}
}
+
+ if (data.DateTimeFormatEntry.MonthGenitiveNames[0] == null)
+ data.DateTimeFormatEntry.MonthGenitiveNames = data.DateTimeFormatEntry.MonthNames;
+
+ if (data.DateTimeFormatEntry.AbbreviatedMonthGenitiveNames[0] == null)
+ data.DateTimeFormatEntry.AbbreviatedMonthGenitiveNames = data.DateTimeFormatEntry.AbbreviatedMonthNames;
+
+
+ }
+
+ // It looks like it never changes
+ data.DateTimeFormatEntry.TimeSeparator = ":";
+
+ // TODO: Don't have input data available but most values are 2 with few exceptions for 1 and 3
+ // We don't add 3 as it's for some arabic states only
+ switch (data.ThreeLetterISOLanguageName) {
+ case "amh":
+ data.NumberFormatEntry.NumberDecimalDigits =
+ data.NumberFormatEntry.PercentDecimalDigits = 1;
+ break;
+ default:
+ data.NumberFormatEntry.NumberDecimalDigits =
+ data.NumberFormatEntry.PercentDecimalDigits = 2;
+ break;
}
+
+ // TODO: For now we capture only native name for default calendar
+ data.NativeCalendarNames[((int) data.CalendarType & 0xFF) - 1] = data.DateTimeFormatEntry.NativeCalendarName;
+
+ var lcdid_value = int.Parse (data.LCID.Substring (2), NumberStyles.HexNumber);
+ Patterns.FillValues (lcdid_value, data);
+
+ return true;
}
- private void LookupNumberSymbols (XPathNavigator nav, CultureInfoEntry ci)
- {
- string dec = (string) nav.Evaluate ("string(symbols/decimal)");
- string group = (string) nav.Evaluate ("string(symbols/group)");
- string percent = (string) nav.Evaluate ("string(symbols/percentSign)");
- string positive = (string) nav.Evaluate ("string(symbols/plusSign)");
- string negative = (string) nav.Evaluate ("string(symbols/minusSign)");
- string per_mille = (string) nav.Evaluate ("string(symbols/perMille)");
- string infinity = (string) nav.Evaluate ("string(symbols/infinity)");
- string nan = (string) nav.Evaluate ("string(symbols/nan)");
-
- if (dec != String.Empty) {
- ci.NumberFormatEntry.NumberDecimalSeparator = dec;
- ci.NumberFormatEntry.PercentDecimalSeparator = dec;
- ci.NumberFormatEntry.CurrencyDecimalSeparator = dec;
- }
-
- if (group != String.Empty) {
- ci.NumberFormatEntry.NumberGroupSeparator = group;
- ci.NumberFormatEntry.PercentGroupSeparator = group;
- ci.NumberFormatEntry.CurrencyGroupSeparator = group;
- }
-
- if (percent != String.Empty)
- ci.NumberFormatEntry.PercentSymbol = percent;
- if (positive != String.Empty)
- ci.NumberFormatEntry.PositiveSign = positive;
- if (negative != String.Empty)
- ci.NumberFormatEntry.NegativeSign = negative;
- if (per_mille != String.Empty)
- ci.NumberFormatEntry.PerMilleSymbol = per_mille;
- if (infinity != String.Empty)
- ci.NumberFormatEntry.PositiveInfinitySymbol = infinity;
- if (nan != String.Empty)
- ci.NumberFormatEntry.NaNSymbol = nan;
- }
-
- private void LookupCurrencySymbol (XPathNavigator nav, CultureInfoEntry ci)
- {
- string type = currency_types [ci.Territory] as string;
-
- if (type == null) {
- Console.WriteLine ("no currency type for: " + ci.Territory);
- return;
- }
-
- string cur = (string) nav.Evaluate ("string(currencies/currency [@type='" +
- type + "']/symbol)");
-
- if (cur != String.Empty)
- ci.NumberFormatEntry.CurrencySymbol = cur;
- }
-
- private bool LookupLcids (CultureInfoEntry ci, bool lang)
+ void Import (XmlDocument doc, CultureInfoEntry ci)
{
- XPathNavigator nav = lcids_doc.CreateNavigator ();
- string name = ci.Name;
- // Language name does not always consist of locale name.
- // (for zh-* it must be either zh-CHS or zh-CHT)
- string langName = GetLanguageFixed (ci);
-
-// if (ci.Territory != null)
-// name += "-" + ci.Territory;
-
- XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("lcids/lcid[@name='"
- + (lang ? langName : name) + "']");
- if (!ni.MoveNext ()) {
- Console.WriteLine ("no lcid found for: {0} ({1}/{2})", name, ci.Language, ci.Territory);
- string file;
-
- if (ci.Territory != null) {
- file = Path.Combine ("locales", ci.Language + "_" + ci.Territory + ".xml");
- Console.WriteLine ("deleting file: " + file);
- File.Delete (file);
- }
+ XmlNodeList nodes;
+ XmlNode el;
+
+ //
+ // Extract script & teritory
+ //
+ el = doc.SelectSingleNode ("ldml/identity/script");
+ if (el != null)
+ ci.Script = el.Attributes["type"].Value;
+
+ el = doc.SelectSingleNode ("ldml/identity/territory");
+ if (el != null)
+ ci.Territory = el.Attributes["type"].Value;
+
+ var df = ci.DateTimeFormatEntry;
+
+ string calendar;
+ // Default calendar is for now always "gregorian"
+ switch (ci.Name) {
+ case "th": case "th-TH":
+ calendar = "buddhist";
+ ci.CalendarType = CalendarType.ThaiBuddhist; // typeof (ThaiBuddhistCalendar);
+ break;
+ case "ar": case "ar-SA":
+ calendar = "islamic";
+ ci.CalendarType = CalendarType.UmAlQuraCalendar; // typeof (UmAlQuraCalendar);
+ break;
+ case "ps": case "ps-AF": case "prs": case "prs-AF": case "dv": case "dv-MV":
+ calendar = "persian";
+ ci.CalendarType = CalendarType.HijriCalendar; // typeof (HijriCalendar);
+ break;
+ default:
+ calendar = "gregorian";
+ ci.CalendarType = CalendarType.Gregorian; // typeof (GregorianCalendar);
+ ci.GregorianCalendarType = GregorianCalendarTypes.Localized;
+ break;
+ }
- return false;
+ var node = doc.SelectSingleNode (string.Format ("ldml/dates/calendars/calendar[@type='{0}']", calendar));
+ if (node != null) {
+ el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/types/type[@type='{0}']", calendar));
+ if (el != null)
+ df.NativeCalendarName = el.InnerText;
+
+
+ // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='wide']"/>
+ nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
+ ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
+ nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='wide']/month");
+ ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
+
+ // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='abbreviated']"/>
+ nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='abbreviated']/month");
+ ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
+ nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='abbreviated']/month");
+ ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
+
+ nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
+ if (nodes != null)
+ ProcessAllNodes (nodes, df.MonthGenitiveNames, AddOrReplaceValue);
+
+ nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='wide']/day");
+ ProcessAllNodes (nodes, df.DayNames, AddOrReplaceDayValue);
+
+ // Apply global rule first <alias source="locale" path="../../dayContext[@type='format']/dayWidth[@type='abbreviated']"/>
+ nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='abbreviated']/day");
+ ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
+ nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='abbreviated']/day");
+ ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
+
+ // TODO: This is not really ShortestDayNames as .NET uses it
+ // Apply global rules first <alias source="locale" path="../../dayContext[@type='stand-alone']/dayWidth[@type='narrow']"/>
+ nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='narrow']/day");
+ ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
+ nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='narrow']/day");
+ ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
+/*
+ Cannot really be used it's too different to .NET and most app rely on it
+
+ el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='full']/dateFormat/pattern");
+ if (el != null)
+ df.LongDatePattern = ConvertDatePatternFormat (el.InnerText);
+
+ // Medium is our short
+ el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='medium']/dateFormat/pattern");
+ if (el != null)
+ df.ShortDatePattern = ConvertDatePatternFormat (el.InnerText);
+
+ // Medium is our Long
+ el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='medium']/timeFormat/pattern");
+ if (el != null)
+ df.LongTimePattern = ConvertTimePatternFormat (el.InnerText);
+
+ el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='short']/timeFormat/pattern");
+ if (el != null)
+ df.ShortTimePattern = ConvertTimePatternFormat (el.InnerText);
+
+ el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='yyyyMMMM']");
+ if (el != null)
+ df.YearMonthPattern = ConvertDatePatternFormat (el.InnerText);
+
+ el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='MMMMdd']");
+ if (el != null)
+ df.MonthDayPattern = ConvertDatePatternFormat (el.InnerText);
+*/
+ el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='am']");
+ if (el == null)
+ // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
+ el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='am']");
+
+ if (el != null)
+ df.AMDesignator = el.InnerText;
+
+ el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='pm']");
+ if (el == null)
+ // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
+ el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='pm']");
+
+ // No data
+ if (el != null)
+ df.PMDesignator = el.InnerText;
}
- string id = ni.Current.GetAttribute ("id", String.Empty);
- string parent = ni.Current.GetAttribute ("parent", String.Empty);
- string specific = ni.Current.GetAttribute ("specific", String.Empty);
- string iso2 = ni.Current.GetAttribute ("iso2", String.Empty);
- string iso3 = ni.Current.GetAttribute ("iso3", String.Empty);
- string win = ni.Current.GetAttribute ("win", String.Empty);
- string icu = ni.Current.GetAttribute ("icu_name", String.Empty);
-
- // lcids are in 0x<hex> format
- ci.Lcid = id;
- ci.ParentLcid = parent;
- ci.SpecificLcid = specific;
- ci.ISO2Lang = iso2;
- ci.ISO3Lang = iso3;
- ci.Win3Lang = win;
- ci.IcuName = icu;
-
- ci.TextInfoEntry = new TextInfoEntry (int.Parse (id.Substring (2), NumberStyles.HexNumber), GetXPathDocument ("textinfo.xml"));
-
- return true;
+ var ni = ci.NumberFormatEntry;
+
+ node = doc.SelectSingleNode ("ldml/numbers/symbols");
+ if (node != null) {
+ el = node.SelectSingleNode ("decimal");
+ if (el != null) {
+ ni.NumberDecimalSeparator =
+ ni.PercentDecimalSeparator =
+ ni.CurrencyDecimalSeparator = el.InnerText;
+ }
+
+ el = node.SelectSingleNode ("plusSign");
+ if (el != null)
+ ni.PositiveSign = el.InnerText;
+
+ el = node.SelectSingleNode ("minusSign");
+ if (el != null)
+ ni.NegativeSign = el.InnerText;
+
+ el = node.SelectSingleNode ("infinity");
+
+ // We cannot use the value from CLDR because many broken
+ // .NET serializers (e.g. JSON) use text value of NegativeInfinity
+ // and different value would break interoperability with .NET
+ if (el != null && el.InnerText != "∞") {
+ ni.InfinitySymbol = el.InnerText;
+ }
+
+ el = node.SelectSingleNode ("perMille");
+ if (el != null)
+ ni.PerMilleSymbol = el.InnerText;
+
+ el = node.SelectSingleNode ("nan");
+ if (el != null)
+ ni.NaNSymbol = el.InnerText;
+
+ el = node.SelectSingleNode ("percentSign");
+ if (el != null)
+ ni.PercentSymbol = el.InnerText;
+
+ el = node.SelectSingleNode ("group");
+ if (el != null) {
+ ni.NumberGroupSeparator =
+ ni.PercentGroupSeparator =
+ ni.CurrencyGroupSeparator = el.InnerText;
+ }
+ }
}
-
- private string LookupFullName (CultureInfoEntry ci, XPathNavigator nav)
+
+ static string ConvertDatePatternFormat (string format)
{
- string pre = "ldml/localeDisplayNames/";
- string ret;
+ //
+ // LDMR uses different characters for some fields
+ // http://unicode.org/reports/tr35/#Date_Format_Patterns
+ //
+ format = format.Replace ("EEEE", "dddd"); // The full name of the day of the week
+ format = format.Replace ("LLLL", "MMMM"); // The full month name
- // FIXME: We use ci.Language here.
- // This is nothing more than hack for nb-NO or nn-NO
- // where Parent of them is nn (not nb or nn).
- ret = (string) nav.Evaluate ("string("+
- pre + "languages/language[@type='" + GetShortName (ci.Language) + "'])");
+ if (format.EndsWith (" y", StringComparison.Ordinal))
+ format += "yyy";
- if (ci.Territory == null)
- return ret;
- ret += " (" + (string) nav.Evaluate ("string("+
- pre + "territories/territory[@type='" + ci.Territory + "'])") + ")";
+ return format;
+ }
- return ret;
+ static string ConvertTimePatternFormat (string format)
+ {
+ format = format.Replace ("a", "tt"); // AM or PM
+ return format;
}
- private void LookupRegions ()
+ static void ProcessAllNodes (XmlNodeList list, IList<string> values, Action<IList<string>, string, string> convertor)
{
- XPathDocument doc = GetXPathDocument ("supplementalData.xml");
- XPathNavigator nav = doc.CreateNavigator ();
- XPathNodeIterator ni = nav.Select ("supplementalData/currencyData/region");
- while (ni.MoveNext ()) {
- string territory = (string) ni.Current.GetAttribute ("iso3166", String.Empty);
- string currency = (string) ni.Current.Evaluate ("string(currency/@iso4217)");
- RegionInfoEntry region = new RegionInfoEntry ();
- region.ISO2Name = territory.ToUpper ();
- region.ISOCurrencySymbol = currency;
- regions [territory] = region;
+ foreach (XmlNode entry in list) {
+ var index = entry.Attributes["type"].Value;
+ var value = entry.InnerText;
+ convertor (values, index, value);
}
+ }
- doc = GetXPathDocument ("langs/en.xml");
- nav = doc.CreateNavigator ();
- ni = nav.Select ("/ldml/localeDisplayNames/territories/territory");
- while (ni.MoveNext ()) {
- RegionInfoEntry r = (RegionInfoEntry)
- regions [ni.Current.GetAttribute ("type", "")];
- if (r == null)
- continue;
- r.EnglishName = ni.Current.Value;
- }
+ // All text indexes are 1-based
+ static void AddOrReplaceValue (IList<string> list, string oneBasedIndex, string value)
+ {
+ int index = int.Parse (oneBasedIndex);
+ AddOrReplaceValue (list, index - 1, value);
+ }
- Hashtable curNames = new Hashtable ();
- ni = nav.Select ("/ldml/numbers/currencies/currency");
- while (ni.MoveNext ())
- curNames [ni.Current.GetAttribute ("type", "")] =
- ni.Current.Evaluate ("string (displayName)");
+ static readonly string[] day_types = new string[] { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
- foreach (RegionInfoEntry r in regions.Values)
- r.CurrencyEnglishName =
- (string) curNames [r.ISOCurrencySymbol];
+ static void AddOrReplaceDayValue (IList<string> list, string dayType, string value)
+ {
+ int index = Array.IndexOf (day_types, dayType);
+ AddOrReplaceValue (list, index, value);
}
- private void LookupCurrencyTypes ()
- {
- XPathDocument doc = GetXPathDocument ("supplementalData.xml");
- XPathNavigator nav = doc.CreateNavigator ();
+ static void AddOrReplaceValue (IList<string> list, int index, string value)
+ {
+ if (list.Count <= index)
+ ((List<string>) list).AddRange (new string[index - list.Count + 1]);
- currency_types = new Hashtable ();
+ list[index] = value;
+ }
- XPathNodeIterator ni =(XPathNodeIterator) nav.Evaluate ("supplementalData/currencyData/region");
- while (ni.MoveNext ()) {
- string territory = (string) ni.Current.GetAttribute ("iso3166", String.Empty);
- string currency = (string) ni.Current.Evaluate ("string(currency/@iso4217)");
- currency_types [territory] = currency;
- }
- }
-
- static string control_chars = "ghmsftz";
-
- // HACK: We are trying to build year_month and month_day patterns from the full pattern.
- private void ParseFullDateFormat (DateTimeFormatEntry df, string full)
- {
-
- string month_day = String.Empty;
- string year_month = String.Empty;
- bool in_month_data = false;
- bool in_year_data = false;
- int month_end = 0;
- int year_end = 0;
- bool inquote = false;
-
- for (int i = 0; i < full.Length; i++) {
- char c = full [i];
- if (!inquote && c == 'M') {
- month_day += c;
- year_month += c;
- in_year_data = true;
- in_month_data = true;
- month_end = month_day.Length;
- year_end = year_month.Length;
- } else if (!inquote && Char.ToLower (c) == 'd') {
- month_day += c;
- in_month_data = true;
- in_year_data = false;
- month_end = month_day.Length;
- } else if (!inquote && Char.ToLower (c) == 'y') {
- year_month += c;
- in_year_data = true;
- in_month_data = false;
- year_end = year_month.Length;
- } else if (!inquote && control_chars.IndexOf (Char.ToLower (c)) >= 0) {
- in_year_data = false;
- in_month_data = false;
- } else if (in_year_data || in_month_data) {
- if (in_month_data)
- month_day += c;
- if (in_year_data)
- year_month += c;
- }
-
- if (c == '\'') {
- inquote = !inquote;
- }
- }
-
- if (month_day != String.Empty) {
- month_day = month_day.Substring (0, month_end);
- df.MonthDayPattern = month_day;
- }
- if (year_month != String.Empty) {
- year_month = year_month.Substring (0, year_end);
- df.YearMonthPattern = year_month;
- }
- }
-
- private class LcidComparer : IComparer {
-
- public int Compare (object a, object b)
- {
- CultureInfoEntry aa = (CultureInfoEntry) a;
- CultureInfoEntry bb = (CultureInfoEntry) b;
-
- return aa.Lcid.CompareTo (bb.Lcid);
- }
- }
-
- private class NameComparer : IComparer {
-
- public int Compare (object a, object b)
- {
- CultureInfoEntry aa = (CultureInfoEntry) a;
- CultureInfoEntry bb = (CultureInfoEntry) b;
-
- return aa.Name.ToLower ().CompareTo (bb.Name.ToLower ());
- }
- }
-
- class RegionComparer : IComparer
+ sealed class LcidComparer : IComparer<CultureInfoEntry>
{
- public static RegionComparer Instance = new RegionComparer ();
-
- public int Compare (object o1, object o2)
+ public int Compare (CultureInfoEntry x, CultureInfoEntry y)
{
- RegionInfoEntry r1 = (RegionInfoEntry) o1;
- RegionInfoEntry r2 = (RegionInfoEntry) o2;
- return String.CompareOrdinal (
- r1.ISO2Name, r2.ISO2Name);
+ return x.LCID.CompareTo (y.LCID);
}
}
- class RegionLCIDMap
+ sealed class ExportNameComparer : IComparer<CultureInfoEntry>
{
- public RegionLCIDMap (int lcid, int regionId)
+ public int Compare (CultureInfoEntry x, CultureInfoEntry y)
{
- LCID = lcid;
- RegionId = regionId;
+ return String.Compare (x.GetExportName (), y.GetExportName (), StringComparison.OrdinalIgnoreCase);
}
+ }
- public int LCID;
- public int RegionId;
+ class RegionComparer : IComparer<RegionInfoEntry>
+ {
+ public int Compare (RegionInfoEntry x, RegionInfoEntry y)
+ {
+ return x.TwoLetterISORegionName.CompareTo (y.TwoLetterISORegionName);
+ }
}
- }
+ }
}
-
-