Driver.cs 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372
  1. //
  2. // Driver.cs
  3. //
  4. // Authors:
  5. // Jackson Harper ([email protected])
  6. // Atsushi Enomoto ([email protected])
  7. // Marek Safar <[email protected]>
  8. //
  9. // (C) 2004-2005 Novell, Inc (http://www.novell.com)
  10. // Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com)
  11. //
  12. // Permission is hereby granted, free of charge, to any person obtaining
  13. // a copy of this software and associated documentation files (the
  14. // "Software"), to deal in the Software without restriction, including
  15. // without limitation the rights to use, copy, modify, merge, publish,
  16. // distribute, sublicense, and/or sell copies of the Software, and to
  17. // permit persons to whom the Software is furnished to do so, subject to
  18. // the following conditions:
  19. //
  20. // The above copyright notice and this permission notice shall be
  21. // included in all copies or substantial portions of the Software.
  22. //
  23. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  24. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  25. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  26. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  27. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  28. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  29. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  30. //
  31. using System;
  32. using System.IO;
  33. using System.Text;
  34. using System.Xml;
  35. using System.Globalization;
  36. using System.Text.RegularExpressions;
  37. using System.Collections.Generic;
  38. using System.Linq;
  39. namespace Mono.Tools.LocaleBuilder
  40. {
  41. public class Driver
  42. {
  43. static readonly string data_root = Path.Combine ("CLDR", "common");
  44. public static void Main (string[] args)
  45. {
  46. Driver d = new Driver ();
  47. ParseArgs (args, d);
  48. d.Run ();
  49. }
  50. private static void ParseArgs (string[] args, Driver d)
  51. {
  52. for (int i = 0; i < args.Length; i++) {
  53. if (args[i] == "--lang" && i + 1 < args.Length)
  54. d.Lang = args[++i];
  55. else if (args[i] == "--locales" && i + 1 < args.Length)
  56. d.Locales = args[++i];
  57. else if (args[i] == "--header" && i + 1 < args.Length)
  58. d.HeaderFileName = args[++i];
  59. else if (args[i] == "--compare")
  60. d.OutputCompare = true;
  61. }
  62. }
  63. private string lang;
  64. private string locales;
  65. private string header_name;
  66. List<CultureInfoEntry> cultures;
  67. Dictionary<string, string> region_currency;
  68. Dictionary<string, string> currency_fractions;
  69. Dictionary<string, string> extra_parent_locales;
  70. // The lang is the language that display names will be displayed in
  71. public string Lang
  72. {
  73. get
  74. {
  75. if (lang == null)
  76. lang = "en";
  77. return lang;
  78. }
  79. set { lang = value; }
  80. }
  81. public string Locales
  82. {
  83. get { return locales; }
  84. set { locales = value; }
  85. }
  86. public string HeaderFileName
  87. {
  88. get
  89. {
  90. if (header_name == null)
  91. return "culture-info-tables.h";
  92. return header_name;
  93. }
  94. set { header_name = value; }
  95. }
  96. public bool OutputCompare { get; set; }
  97. void Print ()
  98. {
  99. cultures.Sort ((a, b) => int.Parse (a.LCID.Substring (2), NumberStyles.HexNumber).CompareTo (int.Parse (b.LCID.Substring (2), NumberStyles.HexNumber)));
  100. var writer = Console.Out;
  101. foreach (var c in cultures) {
  102. writer.WriteLine ("Name: {0}, LCID {1}", c.OriginalName, c.LCID);
  103. writer.WriteLine ("{0}: {1}", "DisplayName", c.DisplayName);
  104. writer.WriteLine ("{0}: {1}", "EnglishName", c.EnglishName);
  105. writer.WriteLine ("{0}: {1}", "NativeName", c.NativeName);
  106. // writer.WriteLine ("{0}: {1}", "OptionalCalendars", c.OptionalCalendars);
  107. writer.WriteLine ("{0}: {1}", "ThreeLetterISOLanguageName", c.ThreeLetterISOLanguageName);
  108. writer.WriteLine ("{0}: {1}", "ThreeLetterWindowsLanguageName", c.ThreeLetterWindowsLanguageName);
  109. writer.WriteLine ("{0}: {1}", "TwoLetterISOLanguageName", c.TwoLetterISOLanguageName);
  110. writer.WriteLine ("{0}: {1}", "Calendar", GetCalendarType (c.CalendarType));
  111. var df = c.DateTimeFormatEntry;
  112. writer.WriteLine ("-- DateTimeFormat --");
  113. Dump (writer, df.AbbreviatedDayNames, "AbbreviatedDayNames");
  114. Dump (writer, df.AbbreviatedMonthGenitiveNames, "AbbreviatedMonthGenitiveNames");
  115. Dump (writer, df.AbbreviatedMonthNames, "AbbreviatedMonthNames");
  116. writer.WriteLine ("{0}: {1}", "AMDesignator", df.AMDesignator);
  117. writer.WriteLine ("{0}: {1}", "CalendarWeekRule", (CalendarWeekRule) df.CalendarWeekRule);
  118. writer.WriteLine ("{0}: {1}", "DateSeparator", df.DateSeparator);
  119. Dump (writer, df.DayNames, "DayNames");
  120. writer.WriteLine ("{0}: {1}", "FirstDayOfWeek", (DayOfWeek) df.FirstDayOfWeek);
  121. // Dump (writer, df.GetAllDateTimePatterns (), "GetAllDateTimePatterns");
  122. // writer.WriteLine ("{0}: {1}", "LongDatePattern", df.LongDatePattern);
  123. // writer.WriteLine ("{0}: {1}", "LongTimePattern", df.LongTimePattern);
  124. writer.WriteLine ("{0}: {1}", "MonthDayPattern", df.MonthDayPattern);
  125. Dump (writer, df.MonthGenitiveNames, "MonthGenitiveNames");
  126. Dump (writer, df.MonthNames, "MonthNames");
  127. writer.WriteLine ("{0}: {1}", "NativeCalendarName", df.NativeCalendarName);
  128. writer.WriteLine ("{0}: {1}", "PMDesignator", df.PMDesignator);
  129. // writer.WriteLine ("{0}: {1}", "ShortDatePattern", df.ShortDatePattern);
  130. Dump (writer, df.ShortestDayNames, "ShortestDayNames");
  131. // writer.WriteLine ("{0}: {1}", "ShortTimePattern", df.ShortTimePattern);
  132. writer.WriteLine ("{0}: {1}", "TimeSeparator", df.TimeSeparator);
  133. // writer.WriteLine ("{0}: {1}", "YearMonthPattern", df.YearMonthPattern);
  134. var ti = c.TextInfoEntry;
  135. writer.WriteLine ("-- TextInfo --");
  136. writer.WriteLine ("{0}: {1}", "ANSICodePage", ti.ANSICodePage);
  137. writer.WriteLine ("{0}: {1}", "EBCDICCodePage", ti.EBCDICCodePage);
  138. writer.WriteLine ("{0}: {1}", "IsRightToLeft", ti.IsRightToLeft);
  139. writer.WriteLine ("{0}: {1}", "ListSeparator", ti.ListSeparator);
  140. writer.WriteLine ("{0}: {1}", "MacCodePage", ti.MacCodePage);
  141. writer.WriteLine ("{0}: {1}", "OEMCodePage", ti.OEMCodePage);
  142. var nf = c.NumberFormatEntry;
  143. writer.WriteLine ("-- NumberFormat --");
  144. writer.WriteLine ("{0}: {1}", "CurrencyDecimalDigits", nf.CurrencyDecimalDigits);
  145. writer.WriteLine ("{0}: {1}", "CurrencyDecimalSeparator", nf.CurrencyDecimalSeparator);
  146. writer.WriteLine ("{0}: {1}", "CurrencyGroupSeparator", nf.CurrencyGroupSeparator);
  147. Dump (writer, nf.CurrencyGroupSizes, "CurrencyGroupSizes", true);
  148. writer.WriteLine ("{0}: {1}", "CurrencyNegativePattern", nf.CurrencyNegativePattern);
  149. writer.WriteLine ("{0}: {1}", "CurrencyPositivePattern", nf.CurrencyPositivePattern);
  150. writer.WriteLine ("{0}: {1}", "CurrencySymbol", nf.CurrencySymbol);
  151. writer.WriteLine ("{0}: {1}", "DigitSubstitution", nf.DigitSubstitution);
  152. writer.WriteLine ("{0}: {1}", "NaNSymbol", nf.NaNSymbol);
  153. Dump (writer, nf.NativeDigits, "NativeDigits");
  154. writer.WriteLine ("{0}: {1}", "NegativeInfinitySymbol", nf.NegativeInfinitySymbol);
  155. writer.WriteLine ("{0}: {1}", "NegativeSign", nf.NegativeSign);
  156. writer.WriteLine ("{0}: {1}", "NumberDecimalDigits", nf.NumberDecimalDigits);
  157. writer.WriteLine ("{0}: {1}", "NumberDecimalSeparator", nf.NumberDecimalSeparator);
  158. writer.WriteLine ("{0}: {1}", "NumberGroupSeparator", nf.NumberGroupSeparator);
  159. Dump (writer, nf.NumberGroupSizes, "NumberGroupSizes", true);
  160. writer.WriteLine ("{0}: {1}", "NumberNegativePattern", nf.NumberNegativePattern);
  161. writer.WriteLine ("{0}: {1}", "PercentNegativePattern", nf.PercentNegativePattern);
  162. writer.WriteLine ("{0}: {1}", "PercentPositivePattern", nf.PercentPositivePattern);
  163. writer.WriteLine ("{0}: {1}", "PercentSymbol", nf.PercentSymbol);
  164. writer.WriteLine ("{0}: {1}", "PerMilleSymbol", nf.PerMilleSymbol);
  165. writer.WriteLine ("{0}: {1}", "PositiveInfinitySymbol", nf.PositiveInfinitySymbol);
  166. writer.WriteLine ("{0}: {1}", "PositiveSign", nf.PositiveSign);
  167. if (c.RegionInfoEntry != null) {
  168. var ri = c.RegionInfoEntry;
  169. writer.WriteLine ("-- RegionInfo --");
  170. writer.WriteLine ("{0}: {1}", "CurrencyEnglishName", ri.CurrencyEnglishName);
  171. writer.WriteLine ("{0}: {1}", "CurrencyNativeName", ri.CurrencyNativeName);
  172. writer.WriteLine ("{0}: {1}", "CurrencySymbol", ri.CurrencySymbol);
  173. writer.WriteLine ("{0}: {1}", "DisplayName", ri.DisplayName);
  174. writer.WriteLine ("{0}: {1}", "EnglishName", ri.EnglishName);
  175. writer.WriteLine ("{0}: {1}", "GeoId", ri.GeoId);
  176. writer.WriteLine ("{0}: {1}", "IsMetric", ri.IsMetric);
  177. writer.WriteLine ("{0}: {1}", "ISOCurrencySymbol", ri.ISOCurrencySymbol);
  178. writer.WriteLine ("{0}: {1}", "Name", ri.Name);
  179. writer.WriteLine ("{0}: {1}", "NativeName", ri.NativeName);
  180. writer.WriteLine ("{0}: {1}", "ThreeLetterISORegionName", ri.ThreeLetterISORegionName);
  181. writer.WriteLine ("{0}: {1}", "ThreeLetterWindowsRegionName", ri.ThreeLetterWindowsRegionName);
  182. writer.WriteLine ("{0}: {1}", "TwoLetterISORegionName", ri.TwoLetterISORegionName);
  183. }
  184. writer.WriteLine ();
  185. }
  186. }
  187. static Type GetCalendarType (CalendarType ct)
  188. {
  189. switch (ct) {
  190. case CalendarType.Gregorian:
  191. return typeof (GregorianCalendar);
  192. case CalendarType.HijriCalendar:
  193. return typeof (HijriCalendar);
  194. case CalendarType.ThaiBuddhist:
  195. return typeof (ThaiBuddhistCalendar);
  196. case CalendarType.UmAlQuraCalendar:
  197. return typeof (UmAlQuraCalendar);
  198. default:
  199. throw new NotImplementedException ();
  200. }
  201. }
  202. static void Dump<T> (TextWriter tw, IList<T> values, string name, bool stopOnNull = false) where T : class
  203. {
  204. tw.Write (name);
  205. tw.Write (": ");
  206. for (int i = 0; i < values.Count; ++i) {
  207. var v = values[i];
  208. if (stopOnNull && v == null)
  209. break;
  210. if (i > 0)
  211. tw.Write (", ");
  212. tw.Write (v);
  213. }
  214. tw.WriteLine ();
  215. }
  216. void Run ()
  217. {
  218. Regex locales_regex = null;
  219. if (Locales != null)
  220. locales_regex = new Regex (Locales);
  221. cultures = new List<CultureInfoEntry> ();
  222. var regions = new List<RegionInfoEntry> ();
  223. var supplemental = GetXmlDocument (Path.Combine (data_root, "supplemental", "supplementalData.xml"));
  224. // Read currencies info
  225. region_currency = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
  226. foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/currencyData/region")) {
  227. var child = entry.SelectSingleNode ("currency");
  228. region_currency.Add (entry.Attributes["iso3166"].Value, child.Attributes["iso4217"].Value);
  229. }
  230. // Parent locales
  231. extra_parent_locales = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
  232. foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/parentLocales/parentLocale")) {
  233. var parent = entry.Attributes["parent"].Value;
  234. if (parent == "root")
  235. continue;
  236. var locales = entry.Attributes["locales"].Value;
  237. foreach (var locale in locales.Split (' '))
  238. extra_parent_locales.Add (locale, parent);
  239. }
  240. // CLDR has habits of completely removing cultures data between release but we don't want to break
  241. // existing code
  242. var knownLCIDs = new HashSet<string> () {
  243. "ar", "bg", "ca", "zh_Hans", "zh_CHS", "cs", "da", "de", "el", "en", "es", "fi", "fr", "he", "hu", "is", "it", "ja", "ko", "nl",
  244. "no", "pl", "pt", "rm", "ro", "ru", "hr", "sk", "sq", "sv", "th", "tr", "ur", "id", "uk", "be", "sl", "et", "lv", "lt", "tg", "fa",
  245. "vi", "hy", "az", "eu", "mk", "st", "ts", "tn", "xh", "zu", "af", "ka", "fo", "hi", "mt", "se", "ga", "ms", "kk", "ky", "sw", "uz",
  246. "bn", "pa", "gu", "or", "ta", "te", "kn", "ml", "as", "mr", "mn", "bo", "cy", "km", "lo", "my", "gl", "kok", "si", "chr", "am", "tzm",
  247. "ne", "ps", "fil", "ff", "ha", "yo", "nso", "kl", "ig", "om", "ti", "haw", "so", "ii", "br", "gsw", "sah", "rw", "gd", "ar_SA", "bg_BG",
  248. "ca_ES", "zh_TW", "cs_CZ", "da_DK", "de_DE", "el_GR", "en_US", "fi_FI", "fr_FR", "he_IL", "hu_HU", "is_IS", "it_IT", "ja_JP", "ko_KR",
  249. "nl_NL", "nb_NO", "pl_PL", "pt_BR", "rm_CH", "ro_RO", "ru_RU", "hr_HR", "sk_SK", "sq_AL", "sv_SE", "th_TH", "tr_TR", "ur_PK", "id_ID",
  250. "uk_UA", "be_BY", "sl_SI", "et_EE", "lv_LV", "lt_LT", "tg_Cyrl_TJ", "fa_IR", "vi_VN", "hy_AM", "az_Latn_AZ", "eu_ES", "mk_MK", "st_ZA",
  251. "ts_ZA", "tn_ZA", "xh_ZA", "zu_ZA", "af_ZA", "ka_GE", "fo_FO", "hi_IN", "mt_MT", "se_NO", "sw_KE", "uz_Latn_UZ", "bn_IN", "gu_IN",
  252. "or_IN", "ta_IN", "te_IN", "kn_IN", "ml_IN", "as_IN", "mr_IN", "bo_CN", "cy_GB", "km_KH", "lo_LA", "my_MM", "gl_ES", "kok_IN", "si_LK",
  253. "am_ET", "ne_NP", "ps_AF", "fil_PH", "ha_Latn_NG", "yo_NG", "nso_ZA", "kl_GL", "ig_NG", "om_ET", "ti_ET", "haw_US", "so_SO", "ii_CN",
  254. "br_FR", "sah_RU", "rw_RW", "gd_GB", "ar_IQ", "zh_CN", "de_CH", "en_GB", "es_MX", "fr_BE", "it_CH", "nl_BE", "nn_NO", "pt_PT", "ro_MD",
  255. "ru_MD", "sv_FI", "ur_IN", "az_Cyrl_AZ", "tn_BW", "ga_IE", "uz_Cyrl_UZ", "bn_BD", "pa_Arab_PK", "ta_LK", "ne_IN", "ti_ER", "ar_EG",
  256. "zh_HK", "de_AT", "en_AU", "es_ES", "fr_CA", "se_FI", "ar_LY", "zh_SG", "de_LU", "en_CA", "es_GT", "fr_CH", "hr_BA", "ar_DZ", "zh_MO",
  257. "de_LI", "en_NZ", "es_CR", "fr_LU", "bs_Latn_BA", "ar_MA", "en_IE", "es_PA", "fr_MC", "sr_Latn_BA", "ar_TN", "en_ZA", "es_DO", "sr_Cyrl_BA",
  258. "ar_OM", "en_JM", "es_VE", "fr_RE", "bs_Cyrl_BA", "ar_YE", "es_CO", "fr_CD", "sr_Latn_RS", "ar_SY", "en_BZ", "es_PE", "fr_SN", "sr_Cyrl_RS",
  259. "ar_JO", "en_TT", "es_AR", "fr_CM", "sr_Latn_ME", "ar_LB", "en_ZW", "es_EC", "fr_CI", "sr_Cyrl_ME", "ar_KW", "en_PH", "es_CL", "fr_ML",
  260. "ar_AE", "es_UY", "fr_MA", "ar_BH", "en_HK", "es_PY", "fr_HT", "ar_QA", "en_IN", "es_BO", "es_SV", "en_SG", "es_HN", "es_NI", "es_PR",
  261. "es_US", "es_CU", "bs_Cyrl", "bs_Latn", "sr_Cyrl", "sr_Latn", "az_Cyrl", "zh", "nn", "bs", "az_Latn", "uz_Cyrl", "mn_Cyrl", "zh_Hant",
  262. "zh_CHT", "nb", "sr", "tg_Cyrl", "uz_Latn", "pa_Arab", "tzm_Latn", "ha_Latn",
  263. "hsb", "tk", "fy", "lb", "ug", "hsb_DE", "ms_MY", "kk_KZ", "ky_KG", "tk_TM", "mn_MN", "fy_NL", "lb_LU", "ug_CN", "gsw_FR", "ca_ES_valencia",
  264. "dsb_DE", "se_SE", "ms_BN", "smn_FI", "en_MY", "smn", "dsb", "tt", "tt_RU"
  265. };
  266. var lcdids = GetXmlDocument ("lcids.xml");
  267. foreach (XmlNode lcid in lcdids.SelectNodes ("lcids/lcid")) {
  268. var name = lcid.Attributes["name"].Value;
  269. if (locales_regex != null && !locales_regex.IsMatch (name))
  270. continue;
  271. var ci = new CultureInfoEntry ();
  272. ci.LCID = lcid.Attributes["id"].Value;
  273. ci.ParentLcid = lcid.Attributes["parent"].Value;
  274. ci.TwoLetterISOLanguageName = lcid.Attributes["iso2"].Value;
  275. ci.ThreeLetterISOLanguageName = lcid.Attributes["iso3"].Value;
  276. ci.ThreeLetterWindowsLanguageName = lcid.Attributes["win"].Value;
  277. ci.OriginalName = name.Replace ('_', '-');
  278. ci.TextInfoEntry = new TextInfoEntry ();
  279. ci.NumberFormatEntry = new NumberFormatEntry ();
  280. if (!Import (ci, name)) {
  281. if (knownLCIDs.Contains (name)) {
  282. Console.WriteLine ($"Missing previously available culture `{ name }' data");
  283. return;
  284. }
  285. continue;
  286. }
  287. if (!knownLCIDs.Contains (name)) {
  288. Console.WriteLine ($"New culture `{ name }' data available");
  289. return;
  290. }
  291. cultures.Add (ci);
  292. }
  293. var doc_english = GetXmlDocument (Path.Combine (data_root, "main", "en.xml"));
  294. //
  295. // Fill all EnglishName values from en.xml language file
  296. //
  297. foreach (var ci in cultures) {
  298. var el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", ci.Language));
  299. if (el != null)
  300. ci.EnglishName = el.InnerText;
  301. string s = null;
  302. if (ci.Script != null) {
  303. el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/scripts/script[@type='{0}']", ci.Script));
  304. if (el != null)
  305. s = el.InnerText;
  306. }
  307. if (ci.Territory != null) {
  308. el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci.Territory));
  309. if (el != null) {
  310. if (s == null)
  311. s = el.InnerText;
  312. else
  313. s = string.Join (", ", s, el.InnerText);
  314. }
  315. }
  316. switch (ci.ThreeLetterWindowsLanguageName) {
  317. case "CHT":
  318. s = "Traditional";
  319. break;
  320. case "CHS":
  321. s = "Simplified";
  322. break;
  323. }
  324. if (s != null)
  325. ci.EnglishName = string.Format ("{0} ({1})", ci.EnglishName, s);
  326. // Special case legacy chinese
  327. if (ci.OriginalName == "zh-CHS" || ci.OriginalName == "zh-CHT")
  328. ci.EnglishName += " Legacy";
  329. // Mono is not localized and supports english only, hence the name will always be same
  330. ci.DisplayName = ci.EnglishName;
  331. }
  332. //
  333. // Fill culture hierarchy for easier data manipulation
  334. //
  335. foreach (var ci in cultures) {
  336. foreach (var p in cultures.Where (l => ci.LCID == l.ParentLcid)) {
  337. ci.Children.Add (p);
  338. }
  339. }
  340. currency_fractions = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
  341. foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/currencyData/fractions/info")) {
  342. currency_fractions.Add (entry.Attributes["iso4217"].Value, entry.Attributes["digits"].Value);
  343. }
  344. var territory2dayofweek = new Dictionary<string, DayOfWeek> (StringComparer.OrdinalIgnoreCase);
  345. foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/firstDay")) {
  346. if (entry.Attributes ["alt"] != null)
  347. continue;
  348. DayOfWeek dow;
  349. switch (entry.Attributes["day"].Value) {
  350. case "mon":
  351. dow = DayOfWeek.Monday;
  352. break;
  353. case "fri":
  354. dow = DayOfWeek.Friday;
  355. break;
  356. case "sat":
  357. dow = DayOfWeek.Saturday;
  358. break;
  359. case "sun":
  360. dow = DayOfWeek.Sunday;
  361. break;
  362. default:
  363. throw new NotImplementedException ();
  364. }
  365. var territories = entry.Attributes["territories"].Value.Split (new [] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
  366. foreach (var t in territories) {
  367. var tr = t.Trim ();
  368. if (tr.Length == 0)
  369. continue;
  370. territory2dayofweek.Add (tr, dow);
  371. }
  372. }
  373. var territory2wr = new Dictionary<string, CalendarWeekRule> (StringComparer.OrdinalIgnoreCase);
  374. foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/weekData/minDays")) {
  375. CalendarWeekRule rule;
  376. switch (entry.Attributes["count"].InnerText) {
  377. case "1":
  378. rule = CalendarWeekRule.FirstDay;
  379. break;
  380. case "4":
  381. rule = CalendarWeekRule.FirstFourDayWeek;
  382. break;
  383. default:
  384. throw new NotImplementedException ();
  385. }
  386. var territories = entry.Attributes["territories"].InnerText.Split ();
  387. foreach (var t in territories)
  388. territory2wr[t] = rule;
  389. }
  390. //
  391. // Fill all territory speficic data where territory is available
  392. //
  393. var non_metric = new HashSet<string> ();
  394. foreach (XmlNode entry in supplemental.SelectNodes ("supplementalData/measurementData/measurementSystem[@type='US']")) {
  395. var territories = entry.Attributes["territories"].InnerText.Split ();
  396. foreach (var t in territories)
  397. non_metric.Add (t);
  398. }
  399. foreach (var ci in cultures) {
  400. if (ci.Territory == null)
  401. continue;
  402. DayOfWeek value;
  403. if (territory2dayofweek.TryGetValue (ci.Territory, out value)) {
  404. ci.DateTimeFormatEntry.FirstDayOfWeek = (int) value;
  405. }
  406. CalendarWeekRule rule;
  407. if (territory2wr.TryGetValue (ci.Territory, out rule)) {
  408. ci.DateTimeFormatEntry.CalendarWeekRule = (int) rule;
  409. }
  410. RegionInfoEntry region = regions.Where (l => l.Name == ci.Territory).FirstOrDefault ();
  411. if (region == null) {
  412. region = new RegionInfoEntry () {
  413. CurrencySymbol = ci.NumberFormatEntry.CurrencySymbol,
  414. EnglishName = ci.EnglishName,
  415. NativeName = ci.NativeTerritoryName,
  416. Name = ci.Territory,
  417. TwoLetterISORegionName = ci.Territory,
  418. CurrencyNativeName = ci.NativeCurrencyName
  419. };
  420. var tc = supplemental.SelectSingleNode (string.Format ("supplementalData/codeMappings/territoryCodes[@type='{0}']", ci.Territory));
  421. region.ThreeLetterISORegionName = tc?.Attributes["alpha3"]?.Value ?? "---";
  422. region.ThreeLetterWindowsRegionName = region.ThreeLetterISORegionName;
  423. var el = doc_english.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", ci.Territory));
  424. region.EnglishName = el.InnerText;
  425. region.DisplayName = region.EnglishName;
  426. string curr;
  427. if (!region_currency.TryGetValue (ci.Territory, out curr))
  428. curr = "---";
  429. region.ISOCurrencySymbol = curr;
  430. el = doc_english.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", region.ISOCurrencySymbol));
  431. region.CurrencyEnglishName = el?.InnerText ?? "---";
  432. if (non_metric.Contains (ci.Territory))
  433. region.IsMetric = false;
  434. var lcdid_value = int.Parse (ci.LCID.Substring (2), NumberStyles.HexNumber);
  435. Patterns.FillValues (lcdid_value, region);
  436. regions.Add (region);
  437. }
  438. string fraction_value;
  439. if (currency_fractions.TryGetValue (region.ISOCurrencySymbol, out fraction_value)) {
  440. ci.NumberFormatEntry.CurrencyDecimalDigits = fraction_value;
  441. }
  442. ci.RegionInfoEntry = region;
  443. }
  444. //
  445. // Fill neutral cultures territory data
  446. //
  447. foreach (var ci in cultures) {
  448. var dtf = ci.DateTimeFormatEntry;
  449. if (dtf.FirstDayOfWeek == null) {
  450. switch (ci.Name) {
  451. case "ar":
  452. dtf.FirstDayOfWeek = (int) DayOfWeek.Saturday;
  453. break;
  454. case "en":
  455. case "pt":
  456. case "zh-Hans":
  457. dtf.FirstDayOfWeek = (int) DayOfWeek.Sunday;
  458. break;
  459. case "es":
  460. case "fr":
  461. case "bn":
  462. case "sr-Cyrl":
  463. case "sr-Latn":
  464. case "ta":
  465. dtf.FirstDayOfWeek = (int) DayOfWeek.Monday;
  466. break;
  467. default:
  468. List<int?> all_fdow = new List<int?> ();
  469. GetAllChildrenValues (ci, all_fdow, l => l.DateTimeFormatEntry.FirstDayOfWeek);
  470. var children = all_fdow.Where (l => l != null).Distinct ().ToList ();
  471. if (children.Count == 1) {
  472. dtf.FirstDayOfWeek = children[0];
  473. } else if (children.Count == 0) {
  474. if (!ci.HasMissingLocale)
  475. Console.WriteLine ("No week data for `{0}'", ci.Name);
  476. // Default to Sunday
  477. dtf.FirstDayOfWeek = (int) DayOfWeek.Sunday;
  478. } else {
  479. // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
  480. // We have to manually disambiguate the correct entry (which is artofficial anyway)
  481. throw new ApplicationException (string.Format ("Ambiguous week data for `{0}'", ci.Name));
  482. }
  483. break;
  484. }
  485. }
  486. if (dtf.CalendarWeekRule == null) {
  487. switch (ci.Name) {
  488. case "ar":
  489. case "en":
  490. case "es":
  491. case "zh-Hans":
  492. case "pt":
  493. case "fr":
  494. case "bn":
  495. dtf.CalendarWeekRule = (int) CalendarWeekRule.FirstDay;
  496. break;
  497. default:
  498. List<int?> all_cwr = new List<int?> ();
  499. GetAllChildrenValues (ci, all_cwr, l => l.DateTimeFormatEntry.CalendarWeekRule);
  500. var children = all_cwr.Where (l => l != null).Distinct ().ToList ();
  501. if (children.Count == 1) {
  502. dtf.CalendarWeekRule = children[0];
  503. } else if (children.Count == 0) {
  504. if (!ci.HasMissingLocale)
  505. Console.WriteLine ("No calendar week data for `{0}'", ci.Name);
  506. // Default to FirstDay
  507. dtf.CalendarWeekRule = (int) CalendarWeekRule.FirstDay;
  508. } else {
  509. // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
  510. // We have to manually disambiguate the correct entry (which is artofficial anyway)
  511. throw new ApplicationException (string.Format ("Ambiguous calendar data for `{0}'", ci.Name));
  512. }
  513. break;
  514. }
  515. }
  516. var nfe = ci.NumberFormatEntry;
  517. if (nfe.CurrencySymbol == null) {
  518. switch (ci.Name) {
  519. case "ar":
  520. nfe.CurrencySymbol = "ر.س.‏";
  521. break;
  522. case "en":
  523. nfe.CurrencySymbol = "$";
  524. break;
  525. case "bs":
  526. nfe.CurrencySymbol = "KM";
  527. break;
  528. case "es":
  529. case "fr":
  530. case "de":
  531. case "it":
  532. case "se":
  533. nfe.CurrencySymbol = "€";
  534. break;
  535. case "hr":
  536. nfe.CurrencySymbol = "kn";
  537. break;
  538. case "pt":
  539. nfe.CurrencySymbol = "R$";
  540. break;
  541. case "sv":
  542. nfe.CurrencySymbol = "kr";
  543. break;
  544. case "ms":
  545. nfe.CurrencySymbol = "RM";
  546. break;
  547. case "bn":
  548. nfe.CurrencySymbol = "টা";
  549. break;
  550. case "sr-Cyrl":
  551. nfe.CurrencySymbol = "Дин.";
  552. break;
  553. case "sr-Latn":
  554. case "sr":
  555. nfe.CurrencySymbol = "Din.";
  556. break;
  557. case "zh":
  558. case "zh-Hans":
  559. nfe.CurrencySymbol = "¥";
  560. break;
  561. case "zh-Hant":
  562. nfe.CurrencySymbol = "HK$";
  563. break;
  564. case "ru":
  565. nfe.CurrencySymbol = "₽";
  566. break;
  567. case "ur":
  568. nfe.CurrencySymbol = "Rs";
  569. break;
  570. case "tn":
  571. nfe.CurrencySymbol = "R";
  572. break;
  573. case "ta":
  574. nfe.CurrencySymbol = "₹";
  575. break;
  576. case "ne":
  577. nfe.CurrencySymbol = "रु";
  578. break;
  579. case "ti":
  580. nfe.CurrencySymbol = "Nfk";
  581. break;
  582. case "ro":
  583. nfe.CurrencySymbol = "RON";
  584. break;
  585. default:
  586. var all_currencies = new List<string> ();
  587. GetAllChildrenValues (ci, all_currencies, l => l.NumberFormatEntry.CurrencySymbol);
  588. var children = all_currencies.Where (l => l != null).Distinct ().ToList ();
  589. if (children.Count == 1) {
  590. nfe.CurrencySymbol = children[0];
  591. } else if (children.Count == 0) {
  592. if (!ci.HasMissingLocale)
  593. Console.WriteLine ("No currency data for `{0}'", ci.Name);
  594. } else {
  595. // .NET has weird concept of territory data available for neutral cultures (e.g. en, es, pt)
  596. // We have to manually disambiguate the correct entry (which is artofficial anyway)
  597. throw new ApplicationException (string.Format ("Ambiguous currency data for `{0}'. Possible values '{1}'", ci.Name, string.Join (", ", children)));
  598. }
  599. break;
  600. }
  601. }
  602. }
  603. if (OutputCompare)
  604. Print ();
  605. regions.Sort (new RegionComparer ());
  606. for (int i = 0; i < regions.Count; ++i)
  607. regions[i].Index = i;
  608. /**
  609. * Dump each table individually. Using StringBuilders
  610. * because it is easier to debug, should switch to just
  611. * writing to streams eventually.
  612. */
  613. using (StreamWriter writer = new StreamWriter (HeaderFileName, false, new UTF8Encoding (false, true))) {
  614. writer.NewLine = "\n";
  615. writer.WriteLine ();
  616. writer.WriteLine ("/* This is a generated file. Do not edit. See tools/locale-builder. */");
  617. writer.WriteLine ("#ifndef MONO_METADATA_CULTURE_INFO_TABLES");
  618. writer.WriteLine ("#define MONO_METADATA_CULTURE_INFO_TABLES 1");
  619. writer.WriteLine ("\n");
  620. writer.WriteLine ("#define NUM_CULTURE_ENTRIES {0}", cultures.Count);
  621. writer.WriteLine ("#define NUM_REGION_ENTRIES {0}", regions.Count);
  622. writer.WriteLine ("\n");
  623. // Sort the cultures by lcid
  624. cultures.Sort (new LcidComparer ());
  625. StringBuilder builder = new StringBuilder ();
  626. int row = 0;
  627. int count = cultures.Count;
  628. for (int i = 0; i < count; i++) {
  629. CultureInfoEntry ci = cultures[i];
  630. if (ci.DateTimeFormatEntry == null)
  631. continue;
  632. ci.DateTimeFormatEntry.AppendTableRow (builder);
  633. ci.DateTimeFormatEntry.Row = row++;
  634. if (i + 1 < count)
  635. builder.Append (',');
  636. builder.Append ('\n');
  637. }
  638. writer.WriteLine ("static const DateTimeFormatEntry datetime_format_entries [] = {");
  639. writer.Write (builder);
  640. writer.WriteLine ("};\n\n");
  641. builder = new StringBuilder ();
  642. row = 0;
  643. for (int i = 0; i < count; i++) {
  644. CultureInfoEntry ci = cultures[i];
  645. if (ci.NumberFormatEntry == null)
  646. continue;
  647. ci.NumberFormatEntry.AppendTableRow (builder);
  648. ci.NumberFormatEntry.Row = row++;
  649. if (i + 1 < count)
  650. builder.Append (',');
  651. builder.Append ('\n');
  652. }
  653. writer.WriteLine ("static const NumberFormatEntry number_format_entries [] = {");
  654. writer.Write (builder);
  655. writer.WriteLine ("};\n\n");
  656. builder = new StringBuilder ();
  657. row = 0;
  658. for (int i = 0; i < count; i++) {
  659. CultureInfoEntry ci = cultures[i];
  660. ci.AppendTableRow (builder);
  661. ci.Row = row++;
  662. if (i + 1 < count)
  663. builder.Append (',');
  664. builder.Append ('\n');
  665. }
  666. writer.WriteLine ("static const CultureInfoEntry culture_entries [] = {");
  667. writer.Write (builder);
  668. writer.WriteLine ("};\n\n");
  669. cultures.Sort (new ExportNameComparer ()); // Sort based on name
  670. builder = new StringBuilder ();
  671. for (int i = 0; i < count; i++) {
  672. CultureInfoEntry ci = cultures[i];
  673. var name = ci.GetExportName ().ToLowerInvariant ();
  674. builder.Append ("\t{" + Entry.EncodeStringIdx (name) + ", ");
  675. builder.Append (ci.Row + "}");
  676. if (i + 1 < count)
  677. builder.Append (',');
  678. builder.AppendFormat ("\t /* {0} */", name);
  679. builder.Append ('\n');
  680. }
  681. writer.WriteLine ("static const CultureInfoNameEntry culture_name_entries [] = {");
  682. writer.Write (builder);
  683. writer.WriteLine ("};\n\n");
  684. builder = new StringBuilder ();
  685. int rcount = 0;
  686. foreach (RegionInfoEntry r in regions) {
  687. r.AppendTableRow (builder);
  688. if (++rcount != regions.Count)
  689. builder.Append (',');
  690. builder.Append ('\n');
  691. }
  692. writer.WriteLine ("static const RegionInfoEntry region_entries [] = {");
  693. writer.Write (builder);
  694. writer.WriteLine ("};\n\n");
  695. builder = new StringBuilder ();
  696. rcount = 0;
  697. foreach (RegionInfoEntry ri in regions) {
  698. builder.Append ("\t{" + Entry.EncodeStringIdx (ri.TwoLetterISORegionName) + ", ");
  699. builder.Append (ri.Index + "}");
  700. if (++rcount != regions.Count)
  701. builder.Append (',');
  702. builder.AppendFormat ("\t /* {0} */", ri.TwoLetterISORegionName);
  703. builder.Append ('\n');
  704. }
  705. writer.WriteLine ("static const RegionInfoNameEntry region_name_entries [] = {");
  706. writer.Write (builder);
  707. writer.WriteLine ("};\n\n");
  708. writer.WriteLine ("static const char locale_strings [] = {");
  709. writer.Write (Entry.General.GetStrings ());
  710. writer.WriteLine ("};\n\n");
  711. writer.WriteLine ("static const char patterns [] = {");
  712. writer.Write (Entry.Patterns.GetStrings ());
  713. writer.WriteLine ("};\n\n");
  714. writer.WriteLine ("static const char datetime_strings [] = {");
  715. writer.Write (Entry.DateTimeStrings.GetStrings ());
  716. writer.WriteLine ("};\n\n");
  717. writer.WriteLine ("#endif\n");
  718. }
  719. }
  720. static void GetAllChildrenValues<T> (CultureInfoEntry entry, List<T> values, Func<CultureInfoEntry, T> selector)
  721. {
  722. foreach (var e in entry.Children) {
  723. if (e == entry)
  724. continue;
  725. values.Add (selector (e));
  726. foreach (var e2 in e.Children) {
  727. GetAllChildrenValues (e2, values, selector);
  728. }
  729. }
  730. }
  731. static XmlDocument GetXmlDocument (string path)
  732. {
  733. var doc = new XmlDocument ();
  734. doc.Load (new XmlTextReader (path) { /*DtdProcessing = DtdProcessing.Ignore*/ } );
  735. return doc;
  736. }
  737. bool Import (CultureInfoEntry data, string locale)
  738. {
  739. string fname = null;
  740. var sep = locale.Split ('_');
  741. data.Language = sep[0];
  742. // CLDR strictly follow ISO names, .NET does not
  743. // Replace names where non-iso2 is used, e.g. Norway
  744. if (data.Language != data.TwoLetterISOLanguageName) {
  745. locale = data.TwoLetterISOLanguageName;
  746. if (sep.Length > 1) {
  747. locale += string.Join ("_", sep.Skip (1));
  748. }
  749. }
  750. // Convert broken Chinese names to correct one
  751. switch (locale) {
  752. case "zh_CHS":
  753. locale = "zh_Hans";
  754. break;
  755. case "zh_CHT":
  756. locale = "zh_Hant";
  757. break;
  758. case "zh_CN":
  759. locale = "zh_Hans_CN";
  760. break;
  761. case "zh_HK":
  762. locale = "zh_Hant_HK";
  763. break;
  764. case "zh_SG":
  765. locale = "zh_Hans_SG";
  766. break;
  767. case "zh_TW":
  768. locale = "zh_Hant_TW";
  769. break;
  770. case "zh_MO":
  771. locale = "zh_Hant_MO";
  772. break;
  773. }
  774. sep = locale.Split ('_');
  775. string full_name = Path.Combine (data_root, "main", locale + ".xml");
  776. if (!File.Exists (full_name)) {
  777. Console.WriteLine ("Missing locale file for `{0}'", locale);
  778. // We could fill default values but that's not as simple as it seems. For instance for non-neutral
  779. // cultures the next part could be territory or not.
  780. return false;
  781. } else {
  782. XmlDocument doc = null;
  783. /*
  784. * Locale generation is done in several steps, first we
  785. * read the root file which is the base invariant data
  786. * then the supplemental root data,
  787. * then the language file, the supplemental languages
  788. * file then the locale file, then the supplemental
  789. * locale file. Values in each descending file can
  790. * overwrite previous values.
  791. */
  792. foreach (var part in sep) {
  793. if (fname != null)
  794. fname += "_";
  795. fname += part;
  796. XmlDocument xml;
  797. string extra;
  798. if (extra_parent_locales.TryGetValue (fname, out extra)) {
  799. xml = GetXmlDocument (Path.Combine (data_root, "main", extra + ".xml"));
  800. if (doc == null)
  801. doc = xml;
  802. Import (xml, data);
  803. }
  804. xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
  805. if (doc == null)
  806. doc = xml;
  807. Import (xml, data);
  808. }
  809. //
  810. // Extract localized locale name from language xml file. Have to do it after both language and territory are read
  811. //
  812. var el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/languages/language[@type='{0}']", data.Language));
  813. if (el != null)
  814. data.NativeName = el.InnerText;
  815. if (data.Territory != null) {
  816. el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/territories/territory[@type='{0}']", data.Territory));
  817. if (el != null) {
  818. // TODO: Should read <localePattern>
  819. data.NativeName = string.Format ("{0} ({1})", data.NativeName, el.InnerText);
  820. data.NativeTerritoryName = el.InnerText;
  821. }
  822. string currency;
  823. // We have territory now we have to run the process again to extract currency symbol
  824. if (region_currency.TryGetValue (data.Territory, out currency)) {
  825. fname = null;
  826. var xml = GetXmlDocument (Path.Combine (data_root, "main", "root.xml"));
  827. el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
  828. if (el != null)
  829. data.NumberFormatEntry.CurrencySymbol = el.InnerText;
  830. foreach (var part in sep) {
  831. if (fname != null)
  832. fname += "_";
  833. fname += part;
  834. xml = GetXmlDocument (Path.Combine (data_root, "main", fname + ".xml"));
  835. el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/symbol", currency));
  836. if (el != null)
  837. data.NumberFormatEntry.CurrencySymbol = el.InnerText;
  838. el = xml.SelectSingleNode (string.Format ("ldml/numbers/currencies/currency[@type='{0}']/displayName", currency));
  839. if (el != null)
  840. data.NativeCurrencyName = el.InnerText;
  841. }
  842. }
  843. }
  844. }
  845. // TODO: Don't have input data available but most values are 2 with few exceptions for 1 and 3
  846. // We don't add 3 as it's for some arabic states only
  847. switch (data.ThreeLetterISOLanguageName) {
  848. case "amh":
  849. data.NumberFormatEntry.NumberDecimalDigits = 1;
  850. break;
  851. default:
  852. data.NumberFormatEntry.NumberDecimalDigits = 2;
  853. break;
  854. }
  855. // TODO: For now we capture only native name for default calendar
  856. data.NativeCalendarNames[((int) data.CalendarType & 0xFF) - 1] = data.DateTimeFormatEntry.NativeCalendarName;
  857. var lcdid_value = int.Parse (data.LCID.Substring (2), NumberStyles.HexNumber);
  858. Patterns.FillValues (lcdid_value, data);
  859. return true;
  860. }
  861. void Import (XmlDocument doc, CultureInfoEntry ci)
  862. {
  863. XmlNodeList nodes;
  864. XmlNode el;
  865. //
  866. // Extract script & teritory
  867. //
  868. el = doc.SelectSingleNode ("ldml/identity/script");
  869. if (el != null)
  870. ci.Script = el.Attributes["type"].Value;
  871. el = doc.SelectSingleNode ("ldml/identity/territory");
  872. if (el != null)
  873. ci.Territory = el.Attributes["type"].Value;
  874. var df = ci.DateTimeFormatEntry;
  875. string calendar;
  876. // Default calendar is for now always "gregorian"
  877. switch (ci.OriginalName) {
  878. case "th": case "th-TH":
  879. calendar = "buddhist";
  880. ci.CalendarType = CalendarType.ThaiBuddhist; // typeof (ThaiBuddhistCalendar);
  881. break;
  882. case "ar": case "ar-SA":
  883. calendar = "islamic";
  884. ci.CalendarType = CalendarType.UmAlQuraCalendar; // typeof (UmAlQuraCalendar);
  885. break;
  886. case "ps": case "ps-AF": case "prs": case "prs-AF": case "dv": case "dv-MV":
  887. calendar = "persian";
  888. ci.CalendarType = CalendarType.HijriCalendar; // typeof (HijriCalendar);
  889. break;
  890. default:
  891. calendar = "gregorian";
  892. ci.CalendarType = CalendarType.Gregorian; // typeof (GregorianCalendar);
  893. ci.GregorianCalendarType = GregorianCalendarTypes.Localized;
  894. break;
  895. }
  896. var node = doc.SelectSingleNode (string.Format ("ldml/dates/calendars/calendar[@type='{0}']", calendar));
  897. if (node != null) {
  898. el = doc.SelectSingleNode (string.Format ("ldml/localeDisplayNames/types/type[@type='{0}']", calendar));
  899. if (el != null)
  900. df.NativeCalendarName = el.InnerText;
  901. // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='wide']"/>
  902. nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
  903. ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
  904. nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='wide']/month");
  905. ProcessAllNodes (nodes, df.MonthNames, AddOrReplaceValue);
  906. if (df.MonthNames != null) {
  907. if (ci.Name == "sv" || ci.Name == "sv-SE") {
  908. ToLower (df.MonthNames);
  909. }
  910. }
  911. // Apply global rule first <alias source="locale" path="../../monthContext[@type='format']/monthWidth[@type='abbreviated']"/>
  912. if (ci.Name == "ja" || ci.Name == "ja-JP") {
  913. // Use common number style
  914. } else {
  915. nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='abbreviated']/month");
  916. ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
  917. nodes = node.SelectNodes ("months/monthContext[@type='stand-alone']/monthWidth[@type='abbreviated']/month");
  918. ProcessAllNodes (nodes, df.AbbreviatedMonthNames, AddOrReplaceValue);
  919. }
  920. if (df.AbbreviatedMonthNames != null) {
  921. if (ci.Name == "sv" || ci.Name == "sv-SE") {
  922. ToLower (df.AbbreviatedMonthNames);
  923. }
  924. }
  925. nodes = node.SelectNodes ("months/monthContext[@type='format']/monthWidth[@type='wide']/month");
  926. if (nodes != null) {
  927. ProcessAllNodes (nodes, df.MonthGenitiveNames, AddOrReplaceValue);
  928. }
  929. // All values seem to match
  930. Array.Copy (df.AbbreviatedMonthNames, df.AbbreviatedMonthGenitiveNames, df.AbbreviatedMonthNames.Length);
  931. nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='wide']/day");
  932. ProcessAllNodes (nodes, df.DayNames, AddOrReplaceDayValue);
  933. // Apply global rule first <alias source="locale" path="../../dayContext[@type='format']/dayWidth[@type='abbreviated']"/>
  934. nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='abbreviated']/day");
  935. ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
  936. nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='abbreviated']/day");
  937. ProcessAllNodes (nodes, df.AbbreviatedDayNames, AddOrReplaceDayValue);
  938. if (df.AbbreviatedDayNames != null) {
  939. if (ci.Name == "sv" || ci.Name == "sv-SE") {
  940. ToLower (df.AbbreviatedDayNames);
  941. }
  942. }
  943. // TODO: This is not really ShortestDayNames as .NET uses it
  944. // Apply global rules first <alias source="locale" path="../../dayContext[@type='stand-alone']/dayWidth[@type='narrow']"/>
  945. nodes = node.SelectNodes ("days/dayContext[@type='format']/dayWidth[@type='narrow']/day");
  946. ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
  947. nodes = node.SelectNodes ("days/dayContext[@type='stand-alone']/dayWidth[@type='narrow']/day");
  948. ProcessAllNodes (nodes, df.ShortestDayNames, AddOrReplaceDayValue);
  949. /*
  950. Cannot really be used it's too different to .NET and most app rely on it
  951. el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='full']/dateFormat/pattern");
  952. if (el != null)
  953. df.LongDatePattern = ConvertDatePatternFormat (el.InnerText);
  954. // Medium is our short
  955. el = node.SelectSingleNode ("dateFormats/dateFormatLength[@type='medium']/dateFormat/pattern");
  956. if (el != null)
  957. df.ShortDatePattern = ConvertDatePatternFormat (el.InnerText);
  958. // Medium is our Long
  959. el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='medium']/timeFormat/pattern");
  960. if (el != null)
  961. df.LongTimePattern = ConvertTimePatternFormat (el.InnerText);
  962. el = node.SelectSingleNode ("timeFormats/timeFormatLength[@type='short']/timeFormat/pattern");
  963. if (el != null)
  964. df.ShortTimePattern = ConvertTimePatternFormat (el.InnerText);
  965. el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='yyyyMMMM']");
  966. if (el != null)
  967. df.YearMonthPattern = ConvertDatePatternFormat (el.InnerText);
  968. el = node.SelectSingleNode ("dateTimeFormats/availableFormats/dateFormatItem[@id='MMMMdd']");
  969. if (el != null)
  970. df.MonthDayPattern = ConvertDatePatternFormat (el.InnerText);
  971. */
  972. el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='am']");
  973. if (el == null)
  974. // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
  975. el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='am']");
  976. // Manual edits for exact .net compatiblity
  977. switch (ci.Name) {
  978. case "en-AU":
  979. df.AMDesignator = "AM";
  980. break;
  981. case "en-NZ":
  982. df.AMDesignator = "a.m.";
  983. break;
  984. case "ko":
  985. case "ko-KP":
  986. case "ko-KR":
  987. df.AMDesignator = "오전";
  988. break;
  989. default:
  990. if (el != null)
  991. df.AMDesignator = el.InnerText;
  992. break;
  993. }
  994. el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='abbreviated']/dayPeriod[@type='pm']");
  995. if (el == null)
  996. // Apply global rule first <alias source="locale" path="../dayPeriodWidth[@type='wide']"/>
  997. el = node.SelectSingleNode ("dayPeriods/dayPeriodContext/dayPeriodWidth[@type='wide']/dayPeriod[@type='pm']");
  998. switch (ci.Name) {
  999. case "en-AU":
  1000. df.PMDesignator = "PM";
  1001. break;
  1002. case "en-NZ":
  1003. df.PMDesignator = "p.m.";
  1004. break;
  1005. case "ko":
  1006. case "ko-KP":
  1007. case "ko-KR":
  1008. df.PMDesignator = "오후";
  1009. break;
  1010. default:
  1011. if (el != null)
  1012. df.PMDesignator = el.InnerText;
  1013. break;
  1014. }
  1015. }
  1016. var ni = ci.NumberFormatEntry;
  1017. node = doc.SelectSingleNode ("ldml/numbers/symbols");
  1018. if (node != null) {
  1019. el = node.SelectSingleNode ("plusSign");
  1020. if (el != null)
  1021. ni.PositiveSign = el.InnerText;
  1022. // CLDR uses unicode negative sign for some culture (e.g sv, is, lt, don't kwnow why) but .NET always
  1023. // uses simple "-" sign and what is worse the parsing code cannot deal with non-ASCII values
  1024. ni.NegativeSign = "-";
  1025. /*
  1026. el = node.SelectSingleNode ("minusSign");
  1027. if (el != null) {
  1028. switch (el.InnerText) {
  1029. case "\u2212":
  1030. case "\u200F\u002D": // Remove any right-to-left mark characters
  1031. case "\u200E\u002D":
  1032. case "\u061C\u2212":
  1033. case "\u200F\u2212":
  1034. ni.NegativeSign = "-";
  1035. break;
  1036. default:
  1037. ni.NegativeSign = el.InnerText;
  1038. break;
  1039. }
  1040. }
  1041. */
  1042. el = node.SelectSingleNode ("infinity");
  1043. // We cannot use the value from CLDR because many broken
  1044. // .NET serializers (e.g. JSON) use text value of NegativeInfinity
  1045. // and different value would break interoperability with .NET
  1046. var inf = GetInfinitySymbol (ci);
  1047. if (inf != null)
  1048. ni.InfinitySymbol = inf;
  1049. else if (el != null && el.InnerText != "∞") {
  1050. ni.InfinitySymbol = el.InnerText;
  1051. }
  1052. el = node.SelectSingleNode ("perMille");
  1053. if (el != null)
  1054. ni.PerMilleSymbol = el.InnerText;
  1055. el = node.SelectSingleNode ("nan");
  1056. if (el != null)
  1057. ni.NaNSymbol = el.InnerText;
  1058. el = node.SelectSingleNode ("percentSign");
  1059. if (el != null)
  1060. ni.PercentSymbol = el.InnerText;
  1061. }
  1062. }
  1063. static void ToLower (string[] values)
  1064. {
  1065. if (values == null)
  1066. return;
  1067. for (int i = 0; i < values.Length; ++i) {
  1068. if (values [i] == null)
  1069. continue;
  1070. values [i] = values [i].ToLower ();
  1071. }
  1072. }
  1073. string GetInfinitySymbol (CultureInfoEntry ci)
  1074. {
  1075. // TODO: Add more
  1076. switch (ci.TwoLetterISOLanguageName) {
  1077. case "ca":
  1078. return "Infinit";
  1079. case "cs":
  1080. case "sk":
  1081. return "+nekonečno";
  1082. case "de":
  1083. return "+unendlich";
  1084. case "el":
  1085. return "Άπειρο";
  1086. case "es":
  1087. case "gl":
  1088. return "Infinito";
  1089. case "it":
  1090. case "pt":
  1091. return "+Infinito";
  1092. case "nl":
  1093. return "oneindig";
  1094. case "fr":
  1095. case "tzm":
  1096. return "+Infini";
  1097. case "pl":
  1098. return "+nieskończoność";
  1099. case "ru":
  1100. case "tg":
  1101. return "бесконечность";
  1102. case "sl":
  1103. return "neskončnost";
  1104. case "rm":
  1105. return "+infinit";
  1106. case "lv":
  1107. return "bezgalība";
  1108. case "lt":
  1109. return "begalybė";
  1110. case "eu":
  1111. return "Infinitu";
  1112. }
  1113. return null;
  1114. }
  1115. static string ConvertDatePatternFormat (string format)
  1116. {
  1117. //
  1118. // LDMR uses different characters for some fields
  1119. // http://unicode.org/reports/tr35/#Date_Format_Patterns
  1120. //
  1121. format = format.Replace ("EEEE", "dddd"); // The full name of the day of the week
  1122. format = format.Replace ("LLLL", "MMMM"); // The full month name
  1123. if (format.EndsWith (" y", StringComparison.Ordinal))
  1124. format += "yyy";
  1125. return format;
  1126. }
  1127. static string ConvertTimePatternFormat (string format)
  1128. {
  1129. format = format.Replace ("a", "tt"); // AM or PM
  1130. return format;
  1131. }
  1132. static void ProcessAllNodes (XmlNodeList list, IList<string> values, Action<IList<string>, string, string> convertor)
  1133. {
  1134. foreach (XmlNode entry in list) {
  1135. var index = entry.Attributes["type"].Value;
  1136. var value = entry.InnerText;
  1137. convertor (values, index, value);
  1138. }
  1139. }
  1140. // All text indexes are 1-based
  1141. static void AddOrReplaceValue (IList<string> list, string oneBasedIndex, string value)
  1142. {
  1143. int index = int.Parse (oneBasedIndex);
  1144. AddOrReplaceValue (list, index - 1, value);
  1145. }
  1146. static readonly string[] day_types = new string[] { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
  1147. static void AddOrReplaceDayValue (IList<string> list, string dayType, string value)
  1148. {
  1149. int index = Array.IndexOf (day_types, dayType);
  1150. AddOrReplaceValue (list, index, value);
  1151. }
  1152. static void AddOrReplaceValue (IList<string> list, int index, string value)
  1153. {
  1154. if (list.Count <= index)
  1155. ((List<string>) list).AddRange (new string[index - list.Count + 1]);
  1156. list[index] = value;
  1157. }
  1158. sealed class LcidComparer : IComparer<CultureInfoEntry>
  1159. {
  1160. public int Compare (CultureInfoEntry x, CultureInfoEntry y)
  1161. {
  1162. return x.LCID.CompareTo (y.LCID);
  1163. }
  1164. }
  1165. sealed class ExportNameComparer : IComparer<CultureInfoEntry>
  1166. {
  1167. public int Compare (CultureInfoEntry x, CultureInfoEntry y)
  1168. {
  1169. return String.Compare (x.GetExportName (), y.GetExportName (), StringComparison.OrdinalIgnoreCase);
  1170. }
  1171. }
  1172. class RegionComparer : IComparer<RegionInfoEntry>
  1173. {
  1174. public int Compare (RegionInfoEntry x, RegionInfoEntry y)
  1175. {
  1176. return x.TwoLetterISORegionName.CompareTo (y.TwoLetterISORegionName);
  1177. }
  1178. }
  1179. }
  1180. }