/**************************************************************************** * * * GNAT COMPILER COMPONENTS * * * * L O C A L E S * * * * C Implementation File * * * * Copyright (C) 2010-2024, Free Software Foundation, Inc. * * * * GNAT is free software; you can redistribute it and/or modify it under * * terms of the GNU General Public License as published by the Free Soft- * * ware Foundation; either version 3, or (at your option) any later ver- * * sion. GNAT is distributed in the hope that it will be useful, but WITH- * * OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * * or FITNESS FOR A PARTICULAR PURPOSE. * * * * As a special exception under Section 7 of GPL version 3, you are granted * * additional permissions described in the GCC Runtime Library Exception, * * version 3.1, as published by the Free Software Foundation. * * * * You should have received a copy of the GNU General Public License and * * a copy of the GCC Runtime Library Exception along with this program; * * see the files COPYING3 and COPYING.RUNTIME respectively. If not, see * * . * * * * GNAT was originally developed by the GNAT team at New York University. * * Extensive contributions were provided by Ada Core Technologies Inc. * * * ****************************************************************************/ /* This file provides OS-dependent support for the Ada.Locales package. */ #include #include #include #define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0])) typedef char char4 [4]; /* Table containing equivalences between ISO_639_1 codes and their ISO_639_3 alpha-3 code plus their language name. */ static char* iso_639[] = { "aa", "aar", "Afar", "ab", "abk", "Abkhazian", "ae", "ave", "Avestan", "af", "afr", "Afrikaans", "ak", "aka", "Akan", "am", "amh", "Amharic", "an", "arg", "Aragonese", "ar", "ara", "Arabic", "as", "asm", "Assamese", "av", "ava", "Avaric", "ay", "aym", "Aymara", "az", "aze", "Azerbaijani", "ba", "bak", "Bashkir", "be", "bel", "Belarusian", "bg", "bul", "Bulgarian", "bi", "bis", "Bislama", "bm", "bam", "Bambara", "bn", "ben", "Bengali", "bo", "bod", "Tibetan", "br", "bre", "Breton", "bs", "bos", "Bosnian", "ca", "cat", "Catalan", "ce", "che", "Chechen", "ch", "cha", "Chamorro", "co", "cos", "Corsican", "cr", "cre", "Cree", "cs", "ces", "Czech", "cu", "chu", "Church Slavic", "cv", "chv", "Chuvash", "cy", "cym", "Welsh", "da", "dan", "Danish", "de", "deu", "German", "dv", "div", "Divehi", "dz", "dzo", "Dzongkha", "ee", "ewe", "Ewe", "el", "ell", "Modern Greek", "en", "eng", "English", "eo", "epo", "Esperanto", "es", "spa", "Spanish", "et", "est", "Estonian", "eu", "eus", "Basque", "fa", "fas", "Persian", "ff", "ful", "Fulah", "fi", "fin", "Finnish", "fj", "fij", "Fijian", "fo", "fao", "Faroese", "fr", "fra", "French", "fy", "fry", "Western Frisian", "ga", "gle", "Irish", "gd", "gla", "Scottish Gaelic", "gl", "glg", "Galician", "gn", "grn", "Guarani", "gu", "guj", "Gujarati", "gv", "glv", "Manx", "ha", "hau", "Hausa", "he", "heb", "Hebrew", "hi", "hin", "Hindi", "ho", "hmo", "Hiri Motu", "hr", "hrv", "Croatian", "ht", "hat", "Haitian", "hu", "hun", "Hungarian", "hy", "hye", "Armenian", "hz", "her", "Herero", "ia", "ina", "Interlingua", "id", "ind", "Indonesian", "ie", "ile", "Interlingue", "ig", "ibo", "Igbo", "ii", "iii", "Sichuan Yi", "ik", "ipk", "Inupiaq", "io", "ido", "Ido", "is", "isl", "Icelandic", "it", "ita", "Italian", "iu", "iku", "Inuktitut", "ja", "jpn", "Japanese", "jv", "jav", "Javanese", "ka", "kat", "Georgian", "kg", "kon", "Kongo", "ki", "kik", "Kikuyu", "kj", "kua", "Kuanyama", "kk", "kaz", "Kazakh", "kl", "kal", "Kalaallisut", "km", "khm", "Central Khmer", "kn", "kan", "Kannada", "ko", "kor", "Korean", "kr", "kau", "Kanuri", "ks", "kas", "Kashmiri", "ku", "kur", "Kurdish", "kv", "kom", "Komi", "kw", "cor", "Cornish", "ky", "kir", "Kirghiz", "la", "lat", "Latin", "lb", "ltz", "Luxembourgish", "lg", "lug", "Ganda", "li", "lim", "Limburgan", "ln", "lin", "Lingala", "lo", "lao", "Lao", "lt", "lit", "Lithuanian", "lu", "lub", "Luba-Katanga", "lv", "lav", "Latvian", "mg", "mlg", "Malagasy", "mh", "mah", "Marshallese", "mi", "mri", "Maori", "mk", "mkd", "Macedonian", "ml", "mal", "Malayalam", "mn", "mon", "Mongolian", "mr", "mar", "Marathi", "ms", "msa", "Malay", "mt", "mlt", "Maltese", "my", "mya", "Burmese", "na", "nau", "Nauru", "nb", "nob", "Norwegian Bokmal", "nd", "nde", "North Ndebele", "ne", "nep", "Nepali", "ng", "ndo", "Ndonga", "nl", "nld", "Dutch", "nn", "nno", "Norwegian Nynorsk", "no", "nor", "Norwegian", "nr", "nbl", "South Ndebele", "nv", "nav", "Navajo", "ny", "nya", "Nyanja", "oc", "oci", "Occitan", "oj", "oji", "Ojibwa", "om", "orm", "Oromo", "or", "ori", "Oriya", "os", "oss", "Ossetian", "pa", "pan", "Panjabi", "pi", "pli", "Pali", "pl", "pol", "Polish", "ps", "pus", "Pushto", "pt", "por", "Portuguese", "qu", "que", "Quechua", "rm", "roh", "Romansh", "rn", "run", "Rundi", "ro", "ron", "Romanian", "ru", "rus", "Russian", "rw", "kin", "Kinyarwanda", "sa", "san", "Sanskrit", "sc", "srd", "Sardinian", "sd", "snd", "Sindhi", "se", "sme", "Northern Sami", "sg", "sag", "Sango", "sh", "hbs", "Serbo-Croatian", "si", "sin", "Sinhala", "sk", "slk", "Slovak", "sl", "slv", "Slovenian", "sm", "smo", "Samoan", "sn", "sna", "Shona", "so", "som", "Somali", "sq", "sqi", "Albanian", "sr", "srp", "Serbian", "ss", "ssw", "Swati", "st", "sot", "Southern Sotho", "su", "sun", "Sundanese", "sv", "swe", "Swedish", "sw", "swa", "Swahili", "ta", "tam", "Tamil", "te", "tel", "Telugu", "tg", "tgk", "Tajik", "th", "tha", "Thai", "ti", "tir", "Tigrinya", "tk", "tuk", "Turkmen", "tl", "tgl", "Tagalog", "tn", "tsn", "Tswana", "to", "ton", "Tonga", "tr", "tur", "Turkish", "ts", "tso", "Tsonga", "tt", "tat", "Tatar", "tw", "twi", "Twi", "ty", "tah", "Tahitian", "ug", "uig", "Uighur", "uk", "ukr", "Ukrainian", "ur", "urd", "Urdu", "uz", "uzb", "Uzbek", "ve", "ven", "Venda", "vi", "vie", "Vietnamese", "vo", "vol", "Volapuk", "wa", "wln", "Walloon", "wo", "wol", "Wolof", "xh", "xho", "Xhosa", "yi", "yid", "Yiddish", "yo", "yor", "Yoruba", "za", "zha", "Zhuang", "zh", "zho", "Chinese", "zu", "zul", "Zulu" }; /* Table containing equivalences between ISO_3166 alpha-2 codes and country names. This table has several entries for codes that have several valid country names. */ static char* iso_3166[] = { "AU", "Australia", "AD", "Andorra", "AE", "United Arab Emirates", "AF", "Afghanistan", "AG", "Antigua and Barbuda", "AI", "Anguilla", "AL", "Albania", "AM", "Armenia", "AN", "Netherlands Antilles", "AO", "Angola", "AQ", "Antarctica", "AR", "Argentina", "AS", "American Samoa", "AT", "Austria", "AU", "Australia", "AW", "Aruba", "AX", "Aland Islands", "AZ", "Azerbaijan", "BA", "Bosnia and Herzegovina", "BB", "Barbados", "BD", "Bangladesh", "BE", "Belgium", "BF", "Burkina Faso", "BG", "Bulgaria", "BH", "Bahrain", "BI", "Burundi", "BJ", "Benin", "BL", "Saint Barthélemy", "BM", "Bermuda", "BN", "Brunei Darussalam", "BO", "Bolivia, Plurinational State of", "BQ", "Bonaire, Sint Eustatius and Saba", "BR", "Brazil", "BS", "Bahamas", "BT", "Bhutan", "BV", "Bouvet Island", "BW", "Botswana", "BY", "Belarus", "BZ", "Belize", "CA", "Canada", "CC", "Cocos (Keeling) Islands", "CD", "Congo, Democratic Republic of the", "CF", "Central African Republic", "CG", "Congo", "CH", "Switzerland", "CI", "Côte d'Ivoire", "CK", "Cook Islands", "CL", "Chile", "CM", "Cameroon", "CN", "China", "CN", "People’s Republic of China", "CN", "PR China", "CN", "PR-China", "CO", "Colombia", "CR", "Costa Rica", "CS", "Czechoslovakia", "CU", "Cuba", "CV", "Cape Verde", "CW", "Curaçao", "CX", "Christmas Island", "CY", "Cyprus", "CZ", "Czech Republic", "DE", "Germany", "DJ", "Djibouti", "DK", "Denmark", "DM", "Dominica", "DO", "Dominican Republic", "DZ", "Algeria", "EC", "Ecuador", "EE", "Estonia", "EG", "Egypt", "EH", "Western Sahara", "ER", "Eritrea", "ES", "Spain", "ET", "Ethiopia", "FI", "Finland", "FG", "Fiji", "FK", "Falkland Islands (Malvinas)", "FM", "Micronesia, Federated States of", "FO", "Faroe Islands", "FR", "France", "GA", "Gabon", "GB", "United Kingdom", "GB", "United-Kingdom", "GB", "England", "GB", "Britain", "GB", "Great Britain", "GD", "Grenada", "GE", "Georgia", "GF", "French Guiana", "GG", "Guernsey", "GH", "Ghana", "GI", "Gibraltar", "GL", "Greenland", "GM", "Gambia", "GN", "Guinea", "GP", "Guadeloupe", "GQ", "Equatorial Guinea", "GR", "Greece", "GS", "South Georgia and the South Sandwich Islands", "GT", "Guatemala", "GU", "Guam", "GW", "Guinea-Bissau", "GY", "Guyana", "HK", "Hong Kong", "HK", "Hong-Kong", "HM", "Heard Island and McDonald Islands", "HN", "Honduras", "HR", "Croatia", "HT", "Haiti", "HU", "Hungary", "ID", "Indonesia", "IE", "Ireland", "IL", "Israel", "IM", "Isle of Man", "IN", "India", "IO", "British Indian Ocean Territory", "IQ", "Iraq", "IR", "Iran", "IR", "Iran, Islamic Republic of", "IS", "Iceland", "IT", "Italy", "JE", "Jersey", "JM", "Jamaica", "JO", "Jordan", "JP", "Japan", "KE", "Kenya", "KG", "Kyrgyzstan", "KH", "Cambodia", "KI", "Kiribati", "KM", "Comoros", "KN", "Saint Kitts and Nevis", "KP", "Korea, Democratic People's Republic of", "KR", "Korea, Republic of", "KW", "Kuwait", "KY", "Cayman Islands", "KZ", "Kazakhstan", "LA", "Lao People's Democratic Republic", "LB", "Lebanon", "LC", "Saint Lucia", "LI", "Liechtenstein", "LK", "Sri Lanka", "LR", "Liberia", "LS", "Lesotho", "LT", "Lithuania", "LU", "Luxembourg", "LV", "Latvia", "LY", "Libya", "MA", "Morocco", "MC", "Monaco", "MD", "Moldova, Republic of", "ME", "Montenegro", "MF", "Saint Martin", "MG", "Madagascar", "MH", "Marshall Islands", "MK", "Macedonia", "ML", "Mali", "MM", "Myanmar", "MN", "Mongolia", "MO", "Macao", "MP", "Northern Mariana Islands", "MQ", "Martinique", "MR", "Mauritania", "MS", "Montserrat", "MT", "Malta", "MU", "Mauritius", "MV", "Maldives", "MW", "Malawi", "MX", "Mexico", "MY", "Malaysia", "MZ", "Mozambique", "NA", "Namibia", "NC", "New Caledonia", "NE", "Niger", "NF", "Norfolk Island", "NG", "Nigeria", "NI", "Nicaragua", "NL", "Netherlands", "NL", "Holland", "NO", "Norway", "NP", "Nepal", "NR", "Nauru", "NU", "Niue", "NZ", "New Zealand", "NZ", "New-Zealand", "OM", "Oman", "PA", "Panama", "PE", "Peru", "PF", "French Polynesia", "PG", "Papua New Guinea", "PH", "Philippines", "PK", "Pakistan", "PL", "Poland", "PM", "Saint Pierre and Miquelon", "PN", "Pitcairn", "PR", "Puerto Rico", "PS", "Palestine, State of", "PT", "Portugal", "PW", "Palau", "PY", "Paraguay", "QA", "Qatar", "RE", "Réunion", "RO", "Romania", "RS", "Serbia", "RU", "Russian Federation", "RW", "Rwanda", "SA", "Saudi Arabia", "SB", "Solomon Islands", "SC", "Seychelles", "SD", "Sudan", "SE", "Sweden", "SG", "Singapore", "SH", "Saint Helena, Ascension and Tristan da Cunha", "SI", "Slovenia", "SJ", "Svalbard and Jan Mayen", "SK", "Slovakia", "SL", "Sierra Leone", "SM", "San Marino", "SN", "Senegal", "SO", "Somalia", "SR", "Suriname", "SS", "South Sudan", "SV", "El Salvador", "SX", "Sint Maarten (Dutch part)", "SY", "Syrian Arab Republic", "SZ", "Swaziland", "TC", "Turks and Caicos Islands", "TD", "Chad", "TF", "French Southern Territories", "TG", "Togo", "TH", "Thailand", "TJ", "Tajikistan", "TK", "Tokelau", "TL", "Timor-Leste", "TM", "Turkmenistan", "TN", "Tunisia", "TO", "Tonga", "TP", "East Timor", "TR", "Turkey", "TT", "Trinidad and Tobago", "TV", "Tuvalu", "TW", "Taiwan", "TW", "Taiwan, Province of China", "TZ", "Tanzania", "TZ", "Tanzania, United Republic of", "UA", "Ukraine", "UG", "Uganda", "UM", "United States Minor Outlying Islands", "US", "United States", "US", "United States of America", "US", "United-States", "UY", "Uruguay", "UZ", "Uzbekistan", "VA", "Holy See (Vatican City State)", "VC", "Saint Vincent and the Grenadines", "VE", "Venezuela", "VE", "Venezuela, Bolivarian Republic of", "VG", "Virgin Islands, British", "VI", "Virgin Islands, U.S.", "VN", "Viet Nam", "VU", "Vanuatu", "WF", "Wallis and Futuna", "WS", "Samoa", "YE", "Yemen", "YT", "Mayotte", "YU", "Yugoslavia", "ZA", "South Africa", "ZM", "Zambia", "ZW", "Zimbabwe" }; /* Utility function to perform case insensitive string comparison. Returns 1 if both strings are equal and 0 otherwise. */ static int str_case_equals (const char *s1, const char *s2) { while (*s1 != '\0' && *s2 != '\0' && tolower(*s1) == tolower(*s2)) { s1++; s2++; } return (*s1 == '\0') && (*s2 == '\0'); } /* Utility function to copy length characters of a string. The target string must have space to store the extra string null terminator. */ static void str_copy (char *target, char *source, int length) { for (; length > 0; source++, target++, length--) { *target = *source; } *target = '\0'; } /* Utility function to search for the last byte of the lc_all string to be processed. Required because in some targets (for example, AIX), the string returned by setlocale() has duplicates. */ static char* str_get_last_byte (char *lc_all) { char* first_space = NULL; char* second_space = NULL; char* last_byte = NULL; char* s1 = lc_all; /* Search for the 1st space (if any) */ while (*s1 != ' ' && *s1 != '\0') s1++; if (*s1 == '\0') { last_byte = s1; } else { first_space = s1; /* Skip this space and search for the 2nd one (if available) */ s1++; while (*s1 != ' ' && *s1 != '\0') s1++; if (*s1 == '\0') { last_byte = s1; } else { second_space=s1; /* Search for the last byte of lc_all */ while (*s1 != '\0') s1++; last_byte = s1; /* Check if the two strings match */ { int len1 = first_space - lc_all; int len2 = second_space - first_space - 1; if (len1 == len2) { char* p1 = lc_all; char* p2 = first_space + 1; /* Compare their contents */ while (*p1 == *p2 && p2 != second_space) { p1++; p2++; } /* if the two strings match then update the last byte */ if (p2 == second_space) { last_byte = first_space; } } } } } return last_byte; } /* Utility function to search in the iso_639_1 table for an iso-639-1 code; returns the corresponding iso-639-3 code or NULL if not found. */ static char* iso_639_1_to_639_3(char* iso_639_1_code) { int len = ARRAY_SIZE (iso_639); char **p = iso_639; int j; for (j=0; j < len/3; j++) { char* s1 = iso_639_1_code; char* s2 = *p; if (s1[0]==s2[0] && s1[1]==s2[1]) { p++; return *p; } p = p + 3; } return NULL; } /* Utility function to search in the iso_639_1 table for a language name; returns the corresponding iso-639-3 code or NULL if not found. */ static char* language_name_to_639_3(char* name) { int len = ARRAY_SIZE (iso_639); char **p = iso_639; int j; p = p + 2; for (j=0; j < len/3; j++) { if (str_case_equals(name, *p)) { p--; return *p; } p = p + 3; } return NULL; } /* Utility function to search in the iso_3166 table for a country name; returns the corresponding iso-3166 code or NULL if not found. */ static char* country_name_to_3166 (char* name) { int len = ARRAY_SIZE (iso_3166); char **p = iso_3166; int j; p++; for (j=0; j < len/2; j++) { if (str_case_equals(name, *p)) { p--; return *p; } p = p + 2; } return NULL; } /* c_get_language_code needs to fill in the Alpha-3 encoding of the language code (3 lowercase letters). That should be "und" if the language is unknown. [see Ada.Locales] */ void c_get_language_code (char4 p) { char* Saved_Locale = setlocale(LC_ALL, NULL); char iso_639_3_code[] = "und"; /* Language Unknown */ char* lc_all; char* result; /* Get locales set in the environment */ setlocale(LC_ALL, ""); lc_all = setlocale(LC_ALL, NULL); /* The string returned by setlocale has the following format: language[_territory][.code-set][@modifier] where language is an ISO 639 language code, territory is an ISO 3166 country code, and codeset is a character set or encoding identifier like ISO-8859-1 or UTF-8. */ if (lc_all != NULL) { char* s = lc_all; int lang_length = 0; /* Copy the language part (which may be an ISO-639-1 code, an ISO-639-3 code, or a language name) adding a string terminator */ while (*s != '_' && *s != '.' && *s != '@' && *s != '\0') s++; lang_length = s - lc_all; /* Handle conversion of ISO-639-1 to ISO-639-3 */ if (lang_length == 2) { char iso_639_1[3]; char* to_iso_639_3; /* Duplicate the ISO-639-1 code adding the null terminator required to search for the equivalent ISO-639-3 code; we cannot just append the null terminator since the pointer may reference non-writable memory. */ str_copy(iso_639_1, lc_all, lang_length); to_iso_639_3 = iso_639_1_to_639_3(iso_639_1); if (to_iso_639_3) str_copy(iso_639_3_code, to_iso_639_3, 3); /* Copy the ISO-639-3 code (adding a null terminator) */ } else if (lang_length == 3) { str_copy(iso_639_3_code, lc_all, lang_length); /* Handle conversion of language name to ISO-639-3 */ } else if (lang_length > 3) { char name_copy[lang_length + 1]; char* to_iso_639_3; /* Duplicate the ISO-639-1 code adding the null terminator required to search for the equivalent ISO-639-3 code; we cannot just append the null terminator since the pointer may reference non-writable memory. */ str_copy(name_copy, lc_all, lang_length); to_iso_639_3 = language_name_to_639_3(name_copy); if (to_iso_639_3) str_copy(iso_639_3_code, to_iso_639_3, 3); } } /* Copy out the computed ISO_639_3 code */ result = iso_639_3_code; for (; *result != '\0'; p++, result++) *p = *result; /* Restore the original locale settings */ setlocale(LC_ALL, Saved_Locale); return; } /* c_get_country_code needs to fill in the Alpha-2 encoding of the country code (2 uppercase letters). That should be "ZZ" if the country is unknown. [see Ada.Locales] */ void c_get_country_code (char4 p) { char* Saved_Locale = setlocale(LC_ALL, NULL); char iso_3166_code[] = "ZZ"; /* Country Unknown */ char* lc_all; char* result; /* Get locales set in the environment */ setlocale(LC_ALL, ""); lc_all = setlocale(LC_ALL, NULL); /* The string returned by setlocale has the following format: language[_territory][.code-set][@modifier] where language is an ISO 639 language code, territory is an ISO 3166 country code, and codeset is a character set or encoding identifier like ISO-8859-1 or UTF-8. */ if (lc_all != NULL) { char* s1 = lc_all; char* s2 = NULL; char* last_byte = str_get_last_byte(lc_all); int country_length = 0; /* Search for the beginning of the country code */ s1 = lc_all; while (*s1 != '_' && *s1 != '.' && *s1 != '@' && s1 != last_byte) s1++; if (*s1 == '_') { s1++; s2 = s1; while (*s2 != '.' && *s2 != '@' && s2 != last_byte) s2++; country_length = s2 - s1; if (country_length == 2) { str_copy(iso_3166_code, s1, country_length); /* setlocale returned us the country name */ } else if (country_length > 3) { char name_copy[country_length + 1]; char* to_3166; str_copy(name_copy, s1, country_length); to_3166 = country_name_to_3166(name_copy); if (to_3166) str_copy(iso_3166_code, to_3166, 2); } } } /* Copy out the computed ISO_3166 code */ result = iso_3166_code; for (; *result != '\0'; p++, result++) *p = *result; /* Restore the original locale settings */ setlocale(LC_ALL, Saved_Locale); return; }